![]() |
Home | Libraries | People | FAQ | More |
這一部分的目標是介紹庫的基本功能。庫中有大量的例外和特殊情況,但我們到以後的章節再討論它們。
本節中我們給出在 STL 算法調用中使用 BLL lambda 表達式的基礎示例。我們以一些簡單的表達式開始並逐步發展。首先,我們初始化一個容器的元素,比方說,一個 list,設為值 1:
list<int> v(10); for_each(v.begin(), v.end(), _1 = 1);
表達式 _1 = 1 創建一個 lambda 仿函數,它將 v 中的每一元素賦值為 1。[1]
接下來,我們創建一個指針的容器,並讓它們指向第一個容器 v 中的元素:
vector<int*> vp(10); transform(v.begin(), v.end(), vp.begin(), &_1);
表達式 &_1 創建一個取得 v 中每一個元素的地址的函數對象。這些地址賦值給 vp 中的對應元素。
下一個代碼片段改變 v 中的值。對於每一個元素,函數 foo 被調用。元素的原始值作為一個參數被傳送給 foo。foo 的結果被賦回給那個元素:
int foo(int); for_each(v.begin(), v.end(), _1 = bind(foo, _1));
下一步是為 vp 中的元素排序:
sort(vp.begin(), vp.end(), *_1 > *_2);
在這個 sort 的調用中,我們根據元素的內容以遞減的順序排列那些元素。
最後,下面的 for_each 調用輸出以換行符分隔的 vp 的排序後的內容:
for_each(vp.begin(), vp.end(), cout << *_1 << '\n');
注意,作為一個 lambda 表達式的子表達式的一個常規(非 lambda 的)表達式立即被求值。這可能讓人覺得驚訝。例如,如果前面的示例被重寫為:
for_each(vp.begin(), vp.end(), cout << '\n' << *_1);
子表達式 cout << '\n' 被立即求值,而效果是輸出一個單個換行符,後面跟著 vp 的元素。BLL 提供函數 constant 和 var 分別將常量和變量引入 lambda 表達式,並可用於防止子表達式的立即求值:
for_each(vp.begin(), vp.end(), cout << constant('\n') << *_1);
關於這些函數的更徹底的描述,參見 「Delaying constants and variables」 部分。
通過 lambda 仿函數的調用,實際參數取代了佔位符。佔位符沒有規定這些實際參數的類型。基本的規則是只要完成取代的 lambda 表達式是一個合法的 C++ 表達式,那麼這個 lambda 函數就能夠用任何類型的參數調用。例如,表達式 _1 + _2 創建一個二元 lambda 仿函數。它能夠被任意類型的兩個對像 A 和 B 調用,只要這兩個對像定義了 operator+(A,B)(而且 BLL 知道這個操作符的返回類型,參見後面的描述)。
C++ 缺乏一個查詢某個表達式類型的機制。然而,這種精確的機制對於 C++ lambda 表達式的實現又是至關重要的。因此,BLL 包含一個稍微複雜的類型推演系統,用一套特徵類來推演 lambda 表達式的結果類型。它處理那些操作數是內建類型的表達式以及大多數操作數是標準庫類型的表達式。大多數用戶定義類型也同樣適用,特別是在被定義的返回類型中的用戶定義操作符符合通常慣例。
然而,還是有一些情況返回類型無法推演。例如,假設你有這樣的定義:
C operator+(A, B);
這個 lambda 表達式調用失敗,因為返回類型無法被推演出來:
A a; B b; (_1 + _2)(a, b);
對此有兩種可供選擇的解決方案。第一種是擴展 BLL 類型推演系統覆蓋你自己的類型(參見 「Extending return type deduction system」 部分)。第二種是使用一個特殊的 lambda 表達式 (ret) 來就地定義返回類型(參見 「Overriding the deduced return type」 部分):
A a; B b; ret<C>(_1 + _2)(a, b);
對於 bind 表達式,返回類型的定義就像是 bind 函數的一個模板參數一樣:
bind<int>(foo, _1, _2);
對實際參數的一個大體限制是它們不能是非常量的右值。例如:
int i = 1; int j = 2; (_1 + _2)(i, j); // ok (_1 + _2)(1, 2); // error (!)
這一限制也許不像看上去那麼糟糕。因為 lambda 仿函數最常用在 STL 算法中,參數來自於很少返回右值的間接引用迭代器和間接引用操作符。對於它們返回右值的情況,相關討論參見 「Rvalues as actual arguments to lambda functors」 部分。
缺省情況下,已綁定參數的 temporary const copies(臨時常拷貝)存儲在 lambda 仿函數中。這就意味著已綁定參數的值在 lambda 函數的創建時是固定的,而且在這個 lambda 函數對象的整個生命週期中也會保持不變。例如:
int i = 1; (_1 = 2, _1 + i)(i);
逗號操作符被重載用來將 lambda 表達式結合到一個序列中,結果是,一元 lambda 仿函數的參數首先被賦為 2,然後再為它加上 i 的值。最終表達式的值是 3,不是 4。換句話說,lambda 表達式由 lambda x.(x = 2, x + 1) 創建,而不是 lambda x.(x = 2, x + i)。
就像我們說過的,這是有例外的缺省行為。嚴格的規則如下:
程序員可以控制使用 ref 和 cref 包裝的存儲機制 [ref]。用 ref 或 cref 包裝一個參數,分別指示庫將參數作為一個引用或常引用來存儲。例如,如果我們重寫前面的示例,並用 ref 包裝變量 i,我們可以創建 lambda 表達式 lambda x.(x = 2, x + i) ,而且表達式最終的值是 4:
i = 1; (_1 = 2, _1 + ref(i))(i);
注意,ref 和 cref 不同於 var 和 constant。後者創建 lambda 仿函數,而前者不創建。例如:
int i; var(i) = 1; // ok ref(i) = 1; // not ok, ref(i) is not a lambda functor
函數 ref 和 cref 的存在主要是由於歷史原因,而且 ref 總是能被 var 取代,而 cref 總能被 constant_ref 取代。關於細節,參見 「Delaying constants and variables」 部分。ref 和 cref 函數是 Boost 中的通用工具函數,因此直接定義在 boost 名字空間中。
數組類型不能被拷貝,因此,缺省情況下,它們作為常引用存儲。
對於某些表達式,參數作為引用存儲更有意義。例如,lambda 表達式 i += _1 的意圖很明顯是要調用 lambda 仿函數以影響變量 i 的值,而不是它的什麼臨時拷貝。再例如,流操作符把它的最左邊的參數作為 non-const references(非常引用)。嚴格規則為:
混合賦值操作符 (+=, *=, etc.) 的左參數作為引向非常量的引用存儲。
如果 << 或 >> 操作符的左參數分別從 basic_ostream 或 basic_istream 的實例派生,則參數作為引向非常量的引用存儲。對於所有其它類型,參數作為一個拷貝存儲。
在指針算數表達式中,非常量數組作為非常量引用來存儲。這是為了防止指針算術運算使非常量數組常量化。
[1] 嚴格地說,C++ 標準將 for_each 定義為一個 non-modifying sequence operation(不變序列操作),而傳給 for_each 的函數對像不應該改變它的參數。其實對 for_each 的參數的要求不必那麼嚴格,因為只要迭代器是 mutable(可變化)的,for_each 也可以接受能夠對它們的參數產生副作用的函數對象。不過,它只是直截了當地提供一個其它帶有 std::for_each 的功能的函數模板,只不過它對它的參數沒有那麼細緻的要求。
| Copyright 1999-2004 Jaakko Jrvi, Gary Powell |