## 用法
與其它許多 Boost 庫一樣,這個庫完全定義在頭文件中,這意味著你不必構建任何東西就可以開始使用。但是,知道一點關于 lambda 表達式的東西肯定是有幫助的。接下來的章節會帶你瀏覽一下這個庫,還包括如何在 lambda 表達式中進行異常處理!這個庫非常廣泛,前面還有很多強大的東西。一個 lambda 表達式通常也稱為匿名函數(_unnamed function_)。它在需要的時 候進行聲明和定義,即就地進行。這非常有用,因為我們常常需要在一個算法中定義另一個算法,這是語言本身所不能支持的。作為替代,我們通過從更大的范圍引 進函數和函數對象來具體定義行為,或者使用嵌套的循環結構,把算法表達式寫入循環中。我們將看到,這正是 lambda 表達式可以發揮的地方。本節內有許多例子,通常例子的一部分是示范如何用"傳統"的編碼方法來解決問題。這樣做的目的是,看看 lambda 表達式在何時以及如何幫助程序寫出更具邏輯且更少的代碼。使用 lambda 表達式的確存在一定的學習曲線,而且它的語法初看起來有點可怕。就象每種新的范式或工具,它們都需要去學習,但是請相信我,得到的好處肯定超過付出的代 價。
### 一個簡單的開始
第一個使用 Boost.Lambda 的程序將會提升你對 lambda 表達式的喜愛。首先,請注意 lambda 類型是聲明在 `boost::lambda` 名字空間中,你需要用一個 using 指示符或 using 聲明來把這些 lambda 聲明帶入你的作用域。包含頭文件 `"boost/lambda/lambda.hpp"` 就可以使用這個庫的主要功能了,對于我們第一個程序這樣已經足夠了。
```
#include <iostream>
#include "boost/lambda/lambda.hpp"
#include "boost/function.hpp"
int main() {
using namespace boost::lambda;
(std::cout << _1 << " " << _3 << " " << _2 << "!\n")
("Hello","friend","my");
boost::function<void(int,int,int)> f=
std::cout << _1 << "*" << _2 << "+" << _3
<< "=" <<_1*_2+_3 << "\n";
f(1,2,3);
f(3,2,1);
}
```
第一個表達式看起來很奇特,你可以在腦子里按著括號來劃分這個表達式;第一部分就是一個 lambda 表達式,它的意思基本上是說,"打印這些參數到 `std::cout`, 但不是立即就做,因為我還不知道這三個參數"。表達式的第二部分才是真正調用這個函數,它說,"嘿!這里有你要的三個參數"。我們再來看看這個表達式的第一部分。
```
std::cout << _1 << " " << _3 << " " << _2 << "!\n"
```
你會注意到表達式中有三個占位符,命名為 `_1`, `_2`, 和 `_3` \[1\]。 這些占位符為 lambda 表達式指出了延后的參數。注意,跟許多函數式編程語言的語法不一樣,創建 lambda 表達式時沒有關鍵字或名字;占位符的出現表明了這是一個 lambda 表達式。所以,這是一個接受三個參數的 lambda 表達式,參數的類型可以是任何支持 `operator<<` 流操作的類型。參數按 1-3-2 的順序打印到 `cout` 。在這個例子中,我們把這個表達式用括號括起來,然后調用得到的這個函數對象,傳遞三個參數給它:`"Hello"`, `"friend"`, 和 `"my"`. 輸出的結果如下:
[1]你可能沒想到象 `_1` 這樣的標識符是合法的,但它們的確是。標識符不能由數字打頭,但可以由下劃線打頭,而數字可以出現在標識符的其它任何地方。
```
Hello my friend!
```
通常,我們要把函數對象傳入算法,這是我們要進一步研究的,但是我們來試驗一些更有用的東西,把 lambda 表達式存入另一個延后調用的函數,名為 `boost::function`. 這個有用的發明將在下一章 "[Library 11](../Text/content.html#ch11): [Function 11](../Text/content.html#ch11)" 中討論,現在你只有知道可以傳遞一個函數或函數對象給 `boost::function` 的實例并保存它以備后用就可以了。在本例中,我們定義了這樣的一個函數 `f`,象這樣:
```
boost::function<void(int,int,int)> f;
```
這個聲明表示 `f` 可以存放用三個參數調用的函數和函數對象,參數的類型全部為 `int`. 然后,我們用一個 lambda 表達式把一個函數對象賦給它,這個表達式表示了算法 _X=S*T+U_, 并且把這個算式及其結果打印到 `cout`.
```
boost::function<void(int,int,int)> f=
std::cout <<
_1 << "*" << _2 << "+" << _3 << "=" <<_1*_2+_3 << "\n";
```
如你所見,在一個表達式中,占位符可以多次使用。我們的函數 `f` 現在可以象一個普通函數那樣調用了,如下:
```
f(1,2,3);
f(3,2,1);
```
運行這段代碼的輸出如下。
```
1*2+3=5
3*2+1=7
```
任意使用標準操作符(操作符還可以被重載!)的表達式都可以用在 lambda 表達式中,并可以保存下來以后調用,或者直接傳遞給某個算法。你要留意,當一個 lambda 表達式沒有使用占位符時(我們還沒有看到如何實現,但的確可以這樣用),那么結果將是一個無參函數(對象)。作為對比,只使用 `_1` 時,結果是一個單參數函數對象;只使用 `_1` 和 `_2` 時,結果則是一個二元函數對象;當只使用 `_1`, `_2`, 和 `_3` 時,結果就是一個三元函數對象。這第一個 lambda 表達式受益于這樣一個事實,即該表達式只使用了內建或常用的C++操作符,這樣就可以直接編寫算法。接下來,我們看看如何綁定表達式到其它函數、類成員函數,甚至是數據成員!
### <a class="calibre36" id="ch10lev2sec3">在操作符不夠用時就用綁定
到目前為止,我們已經看到如果有操作符可以支持我們的表達式,一切順利,但并不總是如此的。有時我們需要把調用另一個函數作為表達式的一部分,這通常要借助于綁定;這種綁定與我們前面在創建 lambda 表達式時見過的綁定有所不同,它需要一個單獨的關鍵字,`bind` (嘿,這真是個聰明的名字!)。一個綁定表達式就是一個被延遲的函數調用,可以是普通函數或成員函數。該函數可以有零個或多個參數,某些參數可以直接設定,另一些則可以在函數調用時給出。對于當前版本的 Boost.Lambda, 最多可支持九個參數(其中三個可以通過使用占位符在稍后給出)。要使用綁定器,你需要包含頭文件`"boost/lambda/bind.hpp"`。
在綁定到一個函數時,第一個參數就是該函數的地址,后面的參數則是函數的參數。對于一個非靜態成員函數,總是有一個隱式的 `this` 參數;在一個 `bind` 表達式中,必須顯式地加上 `this` 參數。為方便起見,不論對象是通過引用傳遞或是通過指針傳遞,語法都是一樣的。因此,在綁定到一個成員函數時,第二個參數(即函數指針后的第一個)就是將要調用該函數的真實對象。綁定到數據成員也是可以的,下面的例子也將有所示范:
```
#include <iostream>
#include <string>
#include <map>
#include <algorithm>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/bind.hpp"
int main() {
using namespace boost::lambda;
typedef std::map<int,std::string> type;
type keys_and_values;
keys_and_values[3]="Less than pi";
keys_and_values[42]="You tell me";
keys_and_values[0]="Nothing, if you ask me";
std::cout << "What's wrong with the following expression?\n";
std::for_each(
keys_and_values.begin(),
keys_and_values.end(),
std::cout << "key=" <<
bind(&type::value_type::first,_1) << ", value="
<< bind(&type::value_type::second,_1) << '\n');
std::cout << "\n...and why does this work as expected?\n";
std::for_each(
keys_and_values.begin(),
keys_and_values.end(),
std::cout << constant("key=") <<
bind(&type::value_type::first,_1) << ", value="
<< bind(&type::value_type::second,_1) << '\n');
std::cout << '\n';
// Print the size and max_size of the container
(std::cout << "keys_and_values.size()=" <<
bind(&type::size,_1) << "\nkeys_and_values.max_size()="
<< bind(&type::max_size,_1))(keys_and_values);
}
```
這個例子開始時先創建一個 `std::map` ,鍵類型為 `int` 且值類型為 `std::string` 。記住,`std::map` 的 `value_type` 是一個由鍵類型和值類型組成的 `std::pair` 。因此,對于我們的 `map`,?`value_type` 就是 `std::pair<int,std::string>`, 所以在 `for_each` 算法中,我們傳入的函數對象要接受一個這樣的類型。給出這個 `pair`, 就可以取出其中的兩個成員(鍵和值),這正是我們第一個 `bind` 表達式所做的。
```
bind(&type::value_type::first,_1)
```
這個表達式生成一個函數對象,它被調用時將取出它的參數,即我們前面討論的 `pair` 中的嵌套類型 `value_type` 的數據成員 `first`。在我們的例子中,`first` 是 `map` 的鍵類型,它是一個 `const int`. 對于成員函數,語法是完全相同的。但你要留意,我們的 lambda 表達式多做了一點;表達式的第一部分是
```
std::cout << "key=" << ...
```
它可以編譯,也可以工作,但它可能不能達到目的。這個表達式不是一個 lambda 表達式;它只是一個表達式而已,再沒有別的了。執行時,它打印 `key=`, 當這個表達式被求值時它僅執行一次,而不是對于每個被 `std::for_each` 所訪問的元素執行一次。在這個例子中,原意是把 `key=` 作為我們的每一個 `keys_and_values`?鍵/值對的前綴。在早一點的那些例子中,我們也是這樣寫的,但那里沒有出現這些問題。原因在于,那里我們用了一個占位符來作為 `operator<<` 的第一個參數,這樣就使得它成為一個有效的 lambda 表達式。而這里,我們必須告訴 Boost.Lambda 要創建一個包含 `"key="` 的函數對象。這就要使用函數 `constant`, 它創建一個無參函數對象,即不帶參數的函數對象;它僅僅保存其參數,然后在被調用時返回它。
```
std::cout << constant("key=") << ...
```
這個小小的修改使得所有輸出都不一樣了,以下是該程序的運行輸出結果。
```
What's wrong with the following expression?
key=0, value=Nothing, if you ask me
3, value=Less than pi
42, value=You tell me
...and why does this work as expected?
key=0, value=Nothing, if you ask me
key=3, value=Less than pi
key=42, value=You tell me
keys_and_values.size()=3
keys_and_values.max_size()=4294967295
```
例子的最后一部分是一個綁定到成員函數的綁定器,而不是綁定到數據成員;語法是一樣的,而且你可以看 到在這兩種情形下,都不需要顯式地表明函數的返回類型。這種奇妙的事情是由于函數或成員函數的返回類型可以被自動推斷,如果是綁定到數據成員,其類型同樣 可以自動得到。但是,有一種情形不能得到返回類型,即當被綁定的是函數對象時;對于普通函數和成員函數,推斷其返回類型是一件簡單的事情\[2\],但對于函數對象則不可能。有兩種方法繞過這個語言的限制,第一種是由 Lambda 庫自己來解決:通過顯式地給出 `bind` 的模板參數來替代返回類型推斷,如下所示。
[2] 你也得小心行事。我們只是說它在技術上可行。
```
class double_it {
public:
int operator()(int i) const {
return i*2;
}
};
int main() {
using namespace boost::lambda;
double_it d;
int i=12;
// If you uncomment the following expression,
// the compiler will complain;
// it's just not possible to deduce the return type
// of the function call operator of double_it.
// (std::cout << _1 << "*2=" << (bind(d,_1)))(i);
(std::cout << _1 << "*2=" << (bind<int>(d,_1)))(i);
(std::cout << _1 << "*2=" << (ret<int>(bind(d,_1))))(i);
}
```
有兩種版本的方法來關閉返回類型推斷系統,短格式的版本只需把返回類型作為模板參數傳給 `bind`, 另一個版本則使用 `ret`, 它要括住不能進行自動推斷的 lambda/bind 表達式。在嵌套的 lambda 表達式中,這很容易會就得乏味,不過還有一種更好的方法可以讓推斷成功。我們將在本章稍后進行介紹。
請注意,一個綁定表達式可以由另一個綁定表達式組成,這使得綁定器成為了進行函數組合的強大工具。嵌套的綁定有許多強大的功能,但是要小心使用,因為這些強大的功能同時也帶來了讀寫以及理解代碼上的額外的復雜性。
### <a class="calibre36" id="ch10lev2sec4">我不喜歡 _1, _2, and _3,我可以用別的名字嗎?
有的人對預定義的占位符名稱不滿意,因此本庫提供了簡便的方法來把它們\[3\]改為任意用戶想用的名字。這是通過聲明一些類型為 `boost::lambda::placeholderX_type` 的變量來實現的,其中 `X` 為 1, 2, 或 3. 例如,假設某人喜歡用 `Arg1`, `Arg2`, 和 `Arg3` 來作占位符的名字:
[3] 技術上,是增加新的名字。
```
#include <iostream>
#include <vector>
#include <string>
#include "boost/lambda/lambda.hpp"
boost::lambda::placeholder1_type Arg1;
boost::lambda::placeholder2_type Arg2;
boost::lambda::placeholder3_type Arg3;
template <typename T,typename Operation>
void for_all(T& t,Operation Op) {
std::for_each(t.begin(),t.end(),Op);
}
int main() {
std::vector<std::string> vec;
vec.push_back("What are");
vec.push_back("the names");
vec.push_back("of the");
vec.push_back("placeholders?");
for_all(vec,std::cout << Arg1 << " ");
std::cout << "\nArg1, Arg2, and Arg3!";
}
```
你定義的占位符變量可以象 `_1`, `_2`, 和 `_3` 一樣使用。另外請注意這里的函數 `for_all` ,它提供了一個簡便的方法,當你經常要對一個容器中的所有元素進行操作時,可以比用 `for_each` 減少一些鍵擊次數。這個函數接受兩個參數:一個容器的引用,以及一個函數或函數對象。該容器中的每個元素將被提供給這個函數或函數對象。我認為它有時會非常有用,也許你也這樣認為。運行這個程序將產生以下輸出:
```
What are the names of the placeholders?
Arg1, Arg2, and Arg3!
```
創建你自己的占位符可能會影響其它閱讀你的代碼的人;多數知道 Boost.Lambda (或 Boost.Bind) 的程序員都熟悉占位符名稱 `_1`, `_2`, 和 `_3`. 如果你決定把它們稱為 `q`, `w`, 和 `e`, 你就需要解釋給你的同事聽它們有什么意思。(而且你可能要經常重復地進行解釋!)
### <a class="calibre36" id="ch10lev2sec5">我想給我的常量和變量命名!
有時,給常量和變量命名可以提高代碼的可讀性。你也記得,我們有時需要創建一個不是立即求值的 lambda 表達式。這時可以使用 `constant` 或 `var`; 它們分別對應于常量或變量。我們已經用過 `constant` 了,基本上 `var` 也是相同的用法。對于復雜或長一些的 lambda 表達式,對一個或多個常量給出名字可以使得表達式更易于理解;對于變量也是如此。要創建命名的常量和變量,你只需要定義一個類型為 `boost::lambda::constant_type<T>::type` 和 `boost::lambda::var_type<T>::type` 的變量,其中的 `T` 為被包裝的常量或變量的類型。看一下以下這個 lambda 表達式的用法:
```
for_all(vec,
std::cout << constant(' ') << _ << constant('\n'));
```
總是使用 `constant` 會很讓人討厭。下面是一個例子,它命名了兩個常量,`newline` 和 `space`,并把它們用于 lambda 表達式。
```
#include <iostream>
#include <vector>
#include <algorithm>
#include "boost/lambda/lambda.hpp"
int main() {
using boost::lambda::constant;
using boost::lambda::constant_type;
constant_type<char>::type newline(constant('\n'));
constant_type<char>::type space(constant(' '));
boost::lambda::placeholder1_type _;
std::vector<int> vec;
vec.push_back(0);
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
for_all(vec,std::cout << space << _ << newline);
for_all(vec,
std::cout << constant(' ') << _ << constant('\n'));
}
```
這是一個避免重復鍵入的好方法,也可以使 lambda 表達式更清楚些。下面是一個類似的例子,首先定義一個類型 `memorizer`, 用于跟蹤曾經賦給它的所有值。然后,用 `var_type` 創建一個命名變量,用于后面的 lambda 表達式。你將會看到命名常量要比命名變量更常用到,但也有些情形會需要使用命名變量\[4\]。
[4] 特別是使用 lambda 循環結構時。
```
#include <iostream>
#include <vector>
#include <algorithm>
#include "boost/lambda/lambda.hpp"
template <typename T> class memorizer {
std::vector<T> vec_;
public:
memorizer& operator=(const T& t) {
vec_.push_back(t);
return *this;
}
void clear() {
vec_.clear();
}
void report() const {
using boost::lambda::_1;
std::for_each(
vec_.begin(),
vec_.end(),
std::cout << _1 << ",");
}
};
int main() {
using boost::lambda::var_type;
using boost::lambda::var;
using boost::lambda::_1;
std::vector<int> vec;
vec.push_back(0);
vec.push_back(1);
vec.push_back(2);
vec.push_back(3);
vec.push_back(4);
memorizer<int> m;
var_type<memorizer<int> >::type mem(var(m));
std::for_each(vec.begin(),vec.end(),mem=_1);
m.report();
m.clear();
std::for_each(vec.begin(),vec.end(),var(m)=_1);
m.report();
}
```
這就是它的全部了,但在你認為自己已經明白了所有東西之前,先回答這個問題:在以下聲明下 `T` 應該是什么類型?
```
constant_type<T>::type hello(constant("Hello"));
```
它是一個 `char*`? 一個 `const char*`? 都不是,它的正確類型是一個含有六個字符(還有一個結束用的空字符)的數組的常量引用,所以我們應該這樣寫:
```
constant_type<const char (&)[6]>::type
hello(constant("Hello"));
```
這很不好看,而且對于需要修改這個字符串的人來說也很痛苦,所以我更愿意使用 `std::string` 來寫。
```
constant_type<std::string>::type
hello_string(constant(std::string("Hello")));
```
這次,你需要比上一次多敲幾個字,但你不需要再計算字符的個數,如果你要改變這個字符串,也沒有問題。
### <a class="calibre36" id="ch10lev2sec6">_ptr_fun_ 和 _mem_fun_ 到哪去了?
也許你還在懷念它們,由于 Boost.Lambda 創建了與標準一致的函數對象,所以沒有必要再記住這些標準庫中的適配器類型了。一個綁定了函數或成員函數的 lambda 表達式可以很好地工作,而且不論綁定的是什么類型,其語法都是一致的。這可以讓代碼更注重其任務而不是某些奇特的語法。以下例子說明了這些好處:
```
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/bind.hpp"
void plain_function(int i) {
std::cout << "void plain_function(" << i << ")\n";
}
class some_class {
public:
void member_function(int i) const {
std::cout <<
"void some_class::member_function(" << i << ") const\n";
}
};
int main() {
std::vector<int> vec(3);
vec[0]=12;
vec[1]=10;
vec[2]=7;
some_class sc;
some_class* psc=≻
// Bind to a free function using ptr_fun
std::for_each(
vec.begin(),
vec.end(),
std::ptr_fun(plain_function));
// Bind to a member function using mem_fun_ref
std::for_each(vec.begin(),vec.end(),
std::bind1st(
std::mem_fun_ref(&some_class::member_function),sc));
// Bind to a member function using mem_fun
std::for_each(vec.begin(),vec.end(),
std::bind1st(
std::mem_fun(&some_class::member_function),psc));
using namespace boost::lambda;
std::for_each(
vec.begin(),
vec.end(),
bind(&plain_function,_1));
std::for_each(vec.begin(),vec.end(),
bind(&some_class::member_function,sc,_1));
std::for_each(vec.begin(),vec.end(),
bind(&some_class::member_function,psc,_1));
}
```
這里真的不需要用 lambda 表達式嗎?相對于使用三個不同的結構來完成同一件事情,我們可以只需向 `bind` 指出要干什么,然后它就會去做。在這個例子中,需要用 `std::bind1st` 來把 `some_class` 的實例綁定到調用中;而對于 Boost.Lambda,這是它工作的一部分。因此,下次你再想是否要用 `ptr_fun`, `mem_fun`, 或 `mem_fun_ref` 時,停下來,使用 Boost.Lambda 來代替它!
### <a class="calibre36" id="ch10lev2sec7">無須_<functional>_的算術操作
我們常常要按順序對一些元素執行算術操作,而標準庫提供了多個函數對象來執行算術操作,如 `plus`, `minus`, `divides`, `modulus`, 等等。但是,這些函數對象需要我們多打很多字,而且常常需要綁定一個參數,這時應該使用綁定器。如果要嵌套這些算術操作,表達式很快就會變得難以使用,而 這正是 lambda 表達式可以發揮巨大作用的地方。因為我們正在處理的是操作符,既是算術上的也是C++術語上的,所以我們有能力使用 lambda 表達式直接編寫我們的算法代碼。作為一個小的動機,考慮一個簡單的問題,對一個數值增加4。然后再考慮另一個問題,完成與標準庫算法(如 `transform`)同樣的工作。雖然第一個問題非常自然,而第二個則完全不一樣(它需要你手工寫循環)。但使用 lambda 表達式,只需關注算法本身。在下例中,我們先使用 `std::bind1st` 和 `std::plus` 對容器中的每個元素加4,然后我們使用 `lambda` 來減4。
```
#include <iostream>
#include <vector>
#include <algorithm>
#include <functional>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/bind.hpp"
int main() {
using namespace boost::lambda;
std::vector<int> vec(3);
vec[0]=12;
vec[1]=10;
vec[2]=7;
// Transform using std::bind1st and std::plus
std::transform(vec.begin(),vec.end(),vec.begin(),
std::bind1st(std::plus<int>(),4));
// Transform using a lambda expression
std::transform(vec.begin(),vec.end(),vec.begin(),_1-=4);
}
```
差別是令人驚訝的!在使用"傳統"方法進行加4時,對于未經訓練的眼睛來說,很難看出究竟在干什么。從代碼中我們看到,我們將一個缺省構造的 `std::plus` 實例的第一個參數綁定到4。而 lambda 表達式則寫成從元素減4。如果你認為使用 `bind1st` 和 `plus` 的版本還不壞,你可以試試更長的表達式。
Boost.Lambda 支持C++中的所有算術操作符,因此幾乎不再需要僅為了算術函數對象而包含 `<functional>` 。以下例子示范了這些算術操作符中某些的用法。`vector vec` 中的每個元素被加法和乘法操作符修改。
```
#include <iostream>
#include <vector>
#include <algorithm>
#include "boost/lambda/lambda.hpp"
int main() {
using namespace boost::lambda;
std::vector<int> vec(3);
vec[0]=1;
vec[1]=2;
vec[2]=3;
std::for_each(vec.begin(),vec.end(),_1+=10);
std::for_each(vec.begin(),vec.end(),_1-=10);
std::for_each(vec.begin(),vec.end(),_1*=3);
std::for_each(vec.begin(),vec.end(),_1/=2);
std::for_each(vec.begin(),vec.end(),_1%=3);
}
```
簡潔、可讀、可維護,這就是使用 Boost.Lambda 所得到的代碼的風格。跳過 `std::plus`, `std::minus`, `std::multiplies`, `std::divides`, 和 `std::modulus`; 使用 Boost.Lambda,你的代碼總會更好。
### <a class="calibre36" id="ch10lev2sec8">編寫可讀的謂詞
標準庫中的許多算法都有一個版本是接受一個一元或二元的謂詞的。這些謂詞是普通函數或函數對象,當 然,lambda 表達式也可以。對于會經常用到的謂詞,當然應該定義函數對象,但通常,它們只使用一兩次并且再不會碰到。在這種情況下,lambda 表達式是更好的選擇,這既是因為代碼可以更容易理解(所有功能都在同一個地方),也是因為代碼不會被一些極少使用的函數對象搞混。作為一個具體的例子,我 們在容器中查找具有某個特定值的元素。如果該元素類型已經定義了 `operator==` ,則可以直接使用算法 `find` ,但如果要使用其它標準來查找元素呢?以下給出類型 `search_for_me` ,你如何使用 `find` 來查找第一個元素,其滿足成員函數 `a` 返回 `"apple"`的條件?
```
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
class search_for_me {
std::string a_;
std::string b_;
public:
search_for_me() {}
search_for_me(const std::string& a,const std::string& b)
: a_(a),b_(b) {}
std::string a() const {
return a_;
}
std::string b() const {
return b_;
}
};
int main() {
std::vector<search_for_me> vec;
vec.push_back(search_for_me("apple","banana"));
vec.push_back(search_for_me("orange","mango"));
std::vector<search_for_me>::iterator it=
std::find_if(vec.begin(),vec.end(),???);
if (it!=vec.end())
std::cout << it->a() << '\n';
}
```
首先,我們需要用 `find_if`,\[5\] 但是標記了 `???` 的地方應該怎樣寫呢?一種辦法是:用一個函數對象來實現該謂詞的邏輯。
[5] `find` 使用 `operator==; find_if` 則要求一個謂詞函數(或函數對象)。
```
class a_finder {
std::string val_;
public:
a_finder() {}
a_finder(const std::string& val) : val_(val) {}
bool operator()(const search_for_me& s) const {
return s.a()==val_;
}
};
```
這個函數對象可以這樣使用:
```
std::vector<search_for_me>::iterator it=
std::find_if(vec.begin(),vec.end(),a_finder("apple"));
```
這可以,但兩分鐘(或幾天)后,我們想要另一個函數對象,這次要測試成員函數 `b`. 等等…這類事情很快就會變得乏味。正如你確信的那樣,這是 lambda 表達式的另一個極好的例子;我們需要某種靈活性,可以在需要的地方和需要的時間直接創建謂詞。我們可以這樣來寫前述的 `find_if` 。
```
std::vector<search_for_me>::iterator it=
std::find_if(vec.begin(),vec.end(),
bind(&search_for_me::a,_1)=="apple");
```
我們 `bind` 到成員函數 `a`, 并且測試它是否等于 `"apple"`,這就是我們的一元謂詞,它就定義在使用的地方。但是等一下,正如它們說的,還有更多的東西。在處理數值類型時,我們可以在所有算術操作符、比較和邏輯操作符中選擇。這意味著哪怕是復雜的謂詞也可以直接了當地定義。仔細閱讀以下代碼,看看謂詞是如何表示的。
```
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include "boost/lambda/lambda.hpp"
int main() {
using namespace boost::lambda;
std::vector<int> vec1;
vec1.push_back(2);
vec1.push_back(3);
vec1.push_back(5);
vec1.push_back(7);
vec1.push_back(11);
std::vector<int> vec2;
vec2.push_back(7);
vec2.push_back(4);
vec2.push_back(2);
vec2.push_back(3);
vec2.push_back(1);
std::cout << *std::find_if(vec1.begin(),vec1.end(),
(_1>=3 && _1<5) || _1<1) << '\n';
std::cout << *std::find_if(vec2.begin(),vec2.end(),
_1>=4 && _1<10) << '\n';
std::cout << *std::find_if(vec1.begin(),vec1.end(),
_1==4 || _1==5) << '\n';
std::cout << *std::find_if(vec2.begin(),vec2.end(),
_1!=7 && _1<10) << '\n';
std::cout << *std::find_if(vec1.begin(),vec1.end(),
!(_1%3)) << '\n';
std::cout << *std::find_if(vec2.begin(),vec2.end(),
_1/2<3) << '\n';
}
```
如你所見,創建這些謂詞就象寫出相應的邏輯一樣容易。這正是我喜歡使用 lambda 表達式的地方,因為它可以被任何人所理解。有時候我們也需要選擇 lambda 表達式以外的機制,因為那些必須理解這些代碼的人的能力;但是在這里,除了增加的價值以外沒有其它了。
### <a class="calibre36" id="ch10lev2sec9">讓你的函數對象可以與 Boost.Lambda 一起使用
不是所有的表達式都適合使用 lambda 表達式,復雜的表達式更適合使用普通的函數對象,而且會多次重用的表達式也應該成為你代碼中的一等公民。它們應該被收集為一個可重用函數對象的庫。但是, 你也可能想把這些函數對象用在 lambda 表達式中,你希望它們可以與 Lambda 一起使用;不是所有函數對象都能做到。問題是函數對象的返回類型不能象普通函數那樣被推斷出來;這是語言的固有限制。但是,有一個定義好的方法來把這個重 要的信息提供給 Lambda 庫,以使得 `bind` 表達式更加干凈。作為這個問題的一個例子,我們看以下函數對象:
```
template <typename T> class add_prev {
T prev_;
public:
T operator()(T t) {
prev_+=t;
return prev_;
}
};
```
對于這樣一個函數對象,lambda 表達式不能推斷出返回類型,因此以下例子不能編譯。
```
#include <iostream>
#include <algorithm>
#include <vector>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/bind.hpp"
int main() {
using namespace boost::lambda;
std::vector<int> vec;
vec.push_back(5);
vec.push_back(8);
vec.push_back(2);
vec.push_back(1);
add_prev<int> ap;
std::transform(
vec.begin(),
vec.end(),
vec.begin(),
bind(var(ap),_1));
}
```
問題在于對 `transform` 的調用。
```
std::transform(vec.begin(),vec.end(),vec.begin(),bind(var(ap),_1));
```
當綁定器被實例化時,返回類型推斷的機制被使用…而且失敗了。因此,這段程序不能通過編譯,你必須顯式地告訴 `bind` 返回類型是什么,象這樣:
```
std::transform(vec.begin(),vec.end(),vec.begin(),
bind<int>(var(ap),_1));
```
這是為 lambda 表達式顯式設置返回類型的正常格式的縮寫,它等價于這段代碼。
```
std::transform(vec.begin(),vec.end(),vec.begin(),
ret<int>(bind<int>(var(ap),_1)));
```
這并不是什么新問題;對于在標準庫算法中使用函數對象都有同樣的問題。在標準庫中,解決的方法是增加 `typedef`s 來表明函數對象的返回類型及參數類型。標準庫還提供了助手類來完成這件事,即類模板 `unary_function` 和 `binary_function`,要讓我們的例子類 `add_prev` 成為合適的函數對象,可以通過定義所需的 `typedef`s (對于一元函數對象,是`argument_type` 和 `result_type`,對于二元函數對象,是`first_argument_type`, `second_argument_type`, 和 `result_type`),也可以通過派生自 `unary_function/binary_function` 來實現。
```
template <typename T> class add_prev : public std::unary_function<T,T>
```
這對于 lambda 表達式是否也足夠好了呢?我們可以簡單地復用這種方法以及我們已有的函數對象嗎?唉,答案是否定的。這種 `typedef` 方法有一個問題:對于泛化的調用操作符,當返回類型或參數類型依賴于模板參數時會怎么樣?或者,當存在多個重載的調用操作符時會怎么樣?由于語言支持模板的 `typedef`s, 這些問題可以解決,但是現在不是這樣的。這就是為什么 Boost.Lambda 需要一個不同的方法,即一個名為 `sig` 的嵌套泛型類。為了讓返回類型推斷可以和 `add_prev` 一起使用,我們象下面那樣定義一個嵌套類型 `sig` :
```
template <typename T> class add_prev :
public std::unary_function<T,T> {
T prev_;
public:
template <typename Args> class sig {
public:
typedef T type;
};
// Rest of definition
```
模板參數 `Args` 實際上是一個 tuple,包含了函數對象(第一個元素)和調用操作符的參數類型。在這個例子中,我們不需要這些信息,返回類型和參數類型都是 `T`. 使用這個改進版本的 `add_prev`, 再不需要在 lambda 表達式中使用返回類型推斷的縮寫,因此我們最早那個版本的代碼現在可以編譯了。
```
std::transform(vec.begin(),vec.end(),vec.begin(),bind(var(ap),_1));
```
我們再來看看 tuple 作為 `sig` 的模板參數是如何工作的,來看另一個有兩個調用操作符的函數對象,其中一個版本接受一個 `int` 參數,另一個版本接受一個 `const std::string` 引用。我們必須要解決的問題是,"如果傳遞給 `sig` 模板的 tuple 的第二個元素類型為 `int`, 則設置返回類型為 `std::string`; 如果傳遞給 `sig` 模板的 tuple 的第二個元素類型為 `std::string`, 則設置返回類型為 `double`"。為此,我們增加一個類模板,我們可以對它進行特化并在 `add_prev::sig` 中使用它。
```
template <typename T> class sig_helper {};
// The version for the overload on int
template<> class sig_helper<int> {
public:
typedef std::string type;
};
// The version for the overload on std::string
template<> class sig_helper<std::string> {
public:
typedef double type;
};
// The function object
class some_function_object {
template <typename Args> class sig {
typedef typename boost::tuples::element<1,Args>::type
cv_first_argument_type;
typedef typename
boost::remove_cv<cv_first_argument_type>::type
first_argument_type;
public:
// The first argument helps us decide the correct version
typedef typename
sig_helper<first_argument_type>::type type;
};
std::string operator()(int i) const {
std::cout << i << '\n';
return "Hello!";
}
double operator()(const std::string& s) const {
std::cout << s << '\n';
return 3.14159265353;
}
};
```
這里有兩個重要的部分要討論:首先是助手類 `sig_helper`, 它由類型 `T` 特化。這個類型可以是 `int` 或 `std::string`, 依賴于要使用哪一個重載版本的調用操作符。通過對這個模板進行全特化,來定義正確的 `typedef` `type`。第二個要注意的部分是 `sig` 類,它的第一個參數(即 tuple 的第二個元素)被取出,并去掉所有的 `const` 或 `volatile` 限定符,結果類型被用于實例化正確版本的 `sig_helper` 類,后者具有正確的 `typedef type`. 這是為我們的類定義返回類型的一種相當復雜(但是必須!)的方法,但是多數情況下,通常都只有一個版本的調用操作符;所以正確地增加嵌套 `sig` 類是一件普通的工作。
我們的函數對象可以在 lambda 表達式中正確使用是很重要的,在需要時定義嵌套 `sig` 類是一個好主意;它很有幫助。
### Lambda 表達式中的控制結構
我們已經看到強大的 lambda 表達式可以很容易地創建,但是許多編程上的問題需要我們可以表示條件,在C++中我們使用 `if`-`then`-`else`, `for`, `while`, 等等。在 Boost.Lambda 中有所有的C++控制結構的 lambda 版本。要使用選擇語句,`if` 和 `switch`, 就分別包含頭文件 `"boost/lambda/if.hpp"` 和 `"boost/lambda/switch.hpp"`。要使用循環語句,`while`, `do`, 和 `for`, 就包含頭文件 `"boost/lambda/loops.hpp"`. 關鍵字不能被重載,所以語法與你前面使用過的有所不同,但是也有很明顯的關聯。作為第一個例子,我們來看看如何在 lambda 表達式中創建一個簡單的 if-then-else 結構。格式是 _if_then_else(_條件_, then-_語句_, else-_語句_)_。還有另外一種語法形式,即 _if_(_條件_)[then-_語句_].else_[else-_語句_]_ 。
```
#include <iostream>
#include <algorithm>
#include <vector>
#include <string>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/bind.hpp"
#include "boost/lambda/if.hpp"
int main() {
using namespace boost::lambda;
std::vector<std::string> vec;
vec.push_back("Lambda");
vec.push_back("expressions");
vec.push_back("really");
vec.push_back("rock");
std::for_each(vec.begin(),vec.end(),if_then_else(
bind(&std::string::size,_1)<=6u,
std::cout << _1 << '\n',
std::cout << constant("Skip.\n")));
std::for_each(vec.begin(),vec.end(),
if_(bind(&std::string::size,_1)<=6u) [
std::cout << _1 << '\n'
]
.else_[
std::cout << constant("Skip.\n")
] );
}
```
如果你是從本章開頭一直讀到這的,你可能會覺得上述代碼非常好讀;但如果你是跳到這來的,就可能覺得驚訝了。控制結構的確增加了閱讀 lambda 表達式的復雜度,它需要更長一點的時間來掌握它的用法。當你掌握了它以后,它就變得很自然了(編寫它們也一樣!)。采用哪一種格式完全取決于你的愛好;它們做得是同一件事。
在上例中,我們有一個 `string` 的 `vector`,如果 `string` 元素的大小小于等于6,它們就被輸出到 `std::cout`; 否則,輸出字符串 `"Skip"`。在這個 `if_then_else` 表達式中有一些東西值得留意。
```
if_then_else(
bind(&std::string::size,_1)<=6u,
std::cout << _1 << '\n',
std::cout << constant("Skip.\n")));
```
首先,條件是一個謂詞,它必須是一個 lambda 表達式!其次,_then_-語句必須也是一個 lambda 表達式!第三,_else_-語句必須也是一個 lambda 表達式!頭兩個都很容易寫出來,但最后一個很容易忘掉用 `constant` 來把字符串("Skip\n")變成一個 lambda 表達式。細心的讀者會注意到例子中使用了 `6u`, 而不是使用 `6`, 這是為了確保執行的是兩個無符號類型的比較。這樣做的原因是,我們使用的是非常深的嵌套模板,這意味著如果這樣一個 lambda 表達式引發了一個編譯器警告,輸出信息將會非常、非常長。你可以試一下去掉這個 `u`,看看你的編譯器會怎樣!你將看到一個關于帶符號類型與無符號類型比較的警告,因為 `std::string::size` 返回一個無符號類型。
控制結構的返回類型是 `void`, 除了 `if_then_else_return`, 它調用條件操作符。讓我們來仔細看看所有控制結構,從 `if` 和 `switch` 開始。記住,要使用 `if`-結構,必須包含 `"boost/lambda/if.hpp"`。對于 `switch`, 必須包含 `"boost/lambda/switch.hpp"`。以下例子都假定名字空間 `boost::lambda` 中的聲明已經通過using聲明或using指令,被帶入當前名字空間。
```
(if_then(_1<5,
std::cout << constant("Less than 5")))(make_const(3));
```
`if_then` 函數以一個條件開始,后跟一個 _then_-部分;在上面的代碼中,如果傳給該 lambda 函數的參數小于5 (`_1<5`), `"Less than 5"` 將被輸出到 `std::cout`. 你會看到如果我們用數值3調用這個 lambda 表達式,我們不能直接傳遞3,象這樣。
```
(if_then(_1<5,std::cout << constant("Less than 5")))(3);
```
這會引起一個編譯錯誤,因為3是一個 `int`, 而一個類型 `int` (或者任何內建類型)的左值不能被 `const` 限定。因此,我們在這里必須使用工具 `make_const`,它只是返回一個對它的參數的 `const` 引用。另一個方法是把整個 lambda 表達式用于調用 `const_parameters`, 象這樣:
```
(const_parameters(
if_then(_1<5,std::cout << constant("Less than 5"))))(3);
```
`const_parameters` 對于避免對多個參數分別進行 `make_const` 非常有用。注意,使用該函數時,lambda 表達式的所有參數都被視為 `const` 引用。
現在來看另一種語法的 `if_then` 。
```
(if_(_1<5)
[std::cout << constant("Less than 5")])(make_const(3));
```
這種寫法更類似于C++關鍵字,但它與 `if_then` 所做的完全一樣。函數 `if_` (注意最后的下劃線)后跟括起來的條件,再后跟 _then_-語句。重復一次,選擇哪種語法完全取決于你的口味。
現在,讓我們來看看 _if-then-else_ 結構;它們與 `if_then` 很相似。
```
(if_then_else(
_1==0,
std::cout << constant("Nothing"),
std::cout << _1))(make_const(0));
(if_(_1==0)
[std::cout << constant("Nothing")].
else_[std::cout << _1])(make_const(0));
```
使用第二種語法增加 else-部分時,要留意 `else_` 前面的點。
lambda 表達式的返回類型是 `void`, 但是有一個版本會返回一個值,它使用條件操作符。對于這種表達式的類型有一些不平常的規則(我在這里略過它們,你可以在 Boost.Lambda 的在線文檔或 C++ 標準[§5.16] 找到詳細的說明)。這里有一個例子,返回值被賦給一個變量,就象你在使用條件操作符一樣。
```
int i;
int value=12;
var(i)=(if_then_else_return
(_1>=10,constant(10),_1))(value);
```
這個結構沒有第二種語法。這些就是 _if-then-else_, 我們再看看 _switch_-語句,它與標準C++ switch有些不同。
```
(switch_statement
_1,
case_statement<0>
(var(std::cout) << "Nothing"),
case_statement<1>
(std::cout << constant("A little")),
default_statement
(std::cout << _1))
)(make_const(100));
```
對 `switch_statement` 的調用從條件變量開始,即我們這里的 `_1`, lambda 表達式的第一個參數。它后跟(最多九個)表現為整型的 case 常量;它們必須是整型的常量表達式。我們提供了兩個這樣的常量,0 和 1 (注意,它們可以是任何可作為整型類型的值)。最手,我們加一個可選的 `default_statement`, 它在 `_1` 不匹配任何一個常量時被執行。注意,在每一個 case 常量后都隱式地增加了一個 `break`-語句,所以無需從 switch 顯式退出(這對于代碼的維護是一件好事\[6\])。
[6] Spokesmen of fall-through case-statements; please excuse this blasphemy.
現在我們來看循環語句,`for`, `while`, 和 `do`. 要使用它們中的任意一個,你必須首先包含頭文件 `"boost/lambda/loops.hpp"`。Boost.Lambda中與C++的 `while` 相對應的是 `while_loop`。
```
int val1=1;
int val2=4;
(while_loop(_1<_2,
(++_1,std::cout << constant("Inc...\n"))))(val1,val2);
```
`while_loop` 語句執行到條件為 `false` 止;這里的條件是 `_1<_2`, 后跟循環體,即表達式 `++_1,std::cout << constant("Inc...\n")`. 當然,條件和循環體本身必須是有效的 lambda 表達式。另一種語法更接近C++語法,就象 `if_` 那樣。
```
int val1=1;
int val2=4;
(while_(_1<_2)
[++_1,std::cout << constant("Inc...\n")])(val1,val2);
```
格式是 `while_(`條件`)[`子語句`]`, 它可以節省不少輸入…但是我個人認為對于 `while` 而言函數調用語法更容易讀,雖然我(不太合理)認為 `if_` 比 `if_then(...)` 容易看。從外表看,`do_while_loop` 與 `while_loop` 非常相似,但它的子語句至少被執行一次(不象 `while`, 它的條件在每次執行后求值)。
```
(do_while_loop(_1!=12,std::cout <<
constant("I'll run once")))(make_const(12));
```
另一種語法是
```
(do_[std::cout <<
constant("I'll run once")].while_(_1!=12))(make_const(12));
```
最后是 `for` 循環的對應,`for_loop`. 在以下例子中,使用了一個命名的延期變量來讓 lambda 表達式更可讀。我們在前面介紹 `constant` 和 `var` 時已經介紹過延期變量。命名的延期變量用于避免重復為常量和變量敲入 `constant` 或 `var` 。它們對你要用的東西進行命名并稍后可被引用。常用的循環格式是 `for_loop`(_init-_語句_,_ 條件_,_ 表達式_,_ 語句),即它與一個普通語句相似,但它是函數的一部分(參數)。
```
int val1=0;
var_type<int>::type counter(var(val1));
(for_loop(counter=0,counter<_1,++counter,var(std::cout)
<< "counter is " << counter << "\n"))(make_const(4));
```
采用另一種語法,語句被分為初始化、條件和表達式。
```
(for_(counter=0,counter<_1,++counter)[var(std::cout)
<< "counter is " << counter << "\n"])(make_const(4));
```
這個例子把延期變量 `counter` 初始化為0,條件為 `counter<_1`, 表達式為 `++counter`.
總結一下本節的控制結構。對于我遇到并使用 lambda 表達式來解決的多數問題,事實上也可以不用它們,但有些時候,它們的確真的是救命者。對于選擇哪一種語法版本,最好的辦法可能是兩種都用,然后感覺一下哪一種最適合于你。要注意的一點是,使用 `switch` 和循環結構時,lambda 表達式很快就會變得很大,如果你還不能熟練使用這個庫,你將很難弄懂這樣的表達式。這時應當小心,如果一個表達式看起來讓你的程序員同事難以分析,不要考慮使用獨立的函數對象(或者讓他們更加熟練地使用 Boost.Lambda!)。
### Lambda 表達式中的類型轉換
在 lambda 表達式中有四種特殊的"轉型操作符"\[7\] 來進行類型的轉換:`ll_dynamic_cast`, `ll_static_cast`, `ll_reinterpret_cast`, 和 `ll_const_cast`. 這些名字與對應的C++關鍵字不一樣,因為它們不能被重載。要使用這些類型轉換,就要包含頭文件 `"boost/lambda/casts.hpp"`. 這些函數與相對應的C++轉型操作符用法類似;它們帶一個顯式的模板參數,即要轉成的目標類型,以及一個隱式的模板參數,即源類型。在我們的第一個例子中,我們將使用兩個類,名為 `base` 和 `derived`. 我們將創建兩個指向 `base` 的指針,一個指向 `base` 實例,另一個指向 `derived` 實例。然后我們嘗試使用 `ll_dynamic_cast` 來從這兩個指針分別獲得一個 `derived*` 。
[7] 技術上,它們是返回函數對象的模板函數。
```
#include <iostream>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/casts.hpp"
#include "boost/lambda/if.hpp"
#include "boost/lambda/bind.hpp"
class base {
public:
virtual ~base() {}
void do_stuff() const {
std::cout << "void base::do_stuff() const\n";
}
};
class derived : public base {
public:
void do_more_stuff() const {
std::cout << "void derived::do_more_stuff() const\n";
}
};
int main() {
using namespace boost::lambda;
base* p1=new base;
base* p2=new derived;
derived* pd=0;
(if_(var(pd)=ll_dynamic_cast<derived*>(_1))
[bind(&derived::do_more_stuff,var(pd))].
else_[bind(&base::do_stuff,*_1)])(p1);
(if_(var(pd)=ll_dynamic_cast<derived*>(_1))
[bind(&derived::do_more_stuff,var(pd))].
else_[bind(&base::do_stuff,*_1)])(p2);
}
```
在 `main` 中,我們做的第一件事情是創建 `p1` 和 `p2`; `p1` 指向一個 `base`, 而 `p2` 則指向一個 `derived`. 在第一個 lambda 表達式中,被賦值的 `pd` 變成了條件;它被隱式轉換為 `bool`, 如果它為?`true`, _then_-部分被求值。這里,我們 `bind` 到成員函數 `do_more_stuff`. 如果 `ll_dynamic_cast` 失敗了,延期變量 `pd` 將為 0, 則 _else_-部分被執行。因此,在我們例子中,lambda 表達式的第一次執行將調用 `base` 上的 `do_stuff` ,而第二次執行則調用 `derived` 上的 `do_more_stuff` ,運行這個程序的輸出如下。
```
void base::do_stuff() const
void derived::do_more_stuff() const
```
注意,在這個例子中,參數 `_1` 被解引用,但這并不是必須的;如果需要它會隱式地完成。如果一個 `bind` 表達式的某個參數必須總是一個指針類型,你可以自己強制對它解引用。否則,把這件雜事留給 Boost.Lambda.
`ll_static_cast` 對于避免警告非常有用。不要用它來抑制重要的信息,但可以用來減少噪音。在前面的一個例子中,我們創建了一個 `bind` 表達式來求一個 `std::string` 的長度(使用 `std::string::size`)并將該長度與另一個整數進行比較。`std::string::size` 的返回類型是一個無符號類型,把它與一個有符號整數進行比較(這很常見),編譯器會產生一個警告,說有符號與無符號的比較是危險的動作。但是,因為這發生 在一個 lambda 表達式中,編譯器忠實地跟蹤到問題的根源,告訴你某個嵌套模板的某部分應對此嚴重問題負責。結果是一個非常長的警告信息,由于低信噪比的原因,它可能會掩 蓋了其它的問題。在泛型代碼中,這有時會成為問題,因為所使用的類型不在我們的控制之內。因而,在評估過可能潛在的問題后,你常會發現使用 `ll_static_cast` 來抑制不想要的警告是有好處的。以下例子包含了示范這種行為的代碼。
```
#include <iostream>
#include <string>
#include <algorithm>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/casts.hpp"
#include "boost/lambda/if.hpp"
#include "boost/lambda/bind.hpp"
template <typename String,typename Integral>
void is_it_long(const String& s,const Integral& i) {
using namespace boost::lambda;
(if_then_else(bind(&String::size,_1)<_2,
var(std::cout) << "Quite short...\n",
std::cout << constant("Quite long...\n")))(s,i);
}
int main() {
std::string s="Is this string long?";
is_it_long(s,4u);
is_it_long(s,4);
}
```
泛型函數 `is_it_long` (請不要關注它是一個有點過于做作的例子)用一個 `Integral` 類型的常數變量引用來執行一個 lambda 表達式。現在,這個類型是有符號的還是無符號的并不在我們的控制之內,因此很有機會某個用戶會不小心引發了一個非常冗長的警告,正如這個例子所示范的,因為用了一個有符號整數調用 `is_it_long` 。
```
is_it_long(s,4);
```
確保用戶不會無意地引發此事(除了要求只能使用無符號類型)的唯一辦法是讓這個參數變成無符號整數類型,不管它原來是什么。這正是 `ll_static_cast` 的工作,因此我們把函數 `is_it_long` 改成這樣:
```
template <typename String,typename Integral>
void is_it_long(const String& s,const Integral& i) {
using namespace boost::lambda;
(if_then_else(bind(&String::size,_1)<
ll_static_cast<typename String::size_type>(_2),
var(std::cout) << "Quite short...\n",
std::cout << constant("Quite long...\n")))(s,i);
}
```
這種情況不會經常發生(至少我沒碰到幾次),但只要它發生了,這種解決方法就可用。`ll_const_cast` 和 `ll_reinterpret_cast` 的用法與我們已經看到的相似,所以就不再舉例了。小心地使用它們,如果沒有非常必要的原因(我想不到有什么原因),盡量不要使用 `ll_reinterpret_cast` 。這是對稱的;如果你需要用它,很大機會你做了一些不應該做的事情。
### 構造與析構
當有必要在 lambda 表達式中創建或銷毀對象時,就需要一些特殊的處理和語法。首先,你不可能獲取構造函數或析構函數的地址,也就不可能對它們使用標準的 `bind` 表達式。此外,操作符 `new` 和 `delete` 有固定的返回類型,因此它們對于任何類型都不能返回 lambda 表達式。如果你需要在 lambda 表達式中創建或銷毀對象,先要確保包含頭文件 `"boost/lambda/construct.hpp"`, 它包含了模板 `constructor`, `destructor`, `new_ptr`, `new_array`, `delete_ptr`, 以及 `delete_array`. 我們來看看如何使用它們,并主要關注 `constructor` 和 `new_ptr`, 它們在構造對象時是最常用的。
我們的第一個例子是一個以智能指針作為元素的容器,我們想在 lambda 表達式中重設智能指針的內容。這通常需要一個對 `operator new` 的調用;例外的情形是使用了客戶化的分配機制,或者是某種工廠方法(factory method)。我們要用 `new_ptr` 來做,如果你想要或者需要的話,通常也可以在賦值表達式中使用 `constructor` 。我們兩種方法都試一下。我們將定義兩個類,`base` 和 `derived`, 以及一個 `boost::shared_ptr<base>` 的 `std::map` ,它以 `std::strings` 為索引。在閱讀本例中的 lambda 表達式之前,先來一下深呼吸;它們將是你在本章所見到的最復雜的兩個 lambda 表達式。雖然復雜,但是要理解它們是干什么的還是很明白的。只是要花你一點時間。
```
#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/construct.hpp"
#include "boost/lambda/bind.hpp"
#include "boost/lambda/if.hpp"
#include "boost/lambda/casts.hpp"
#include "boost/shared_ptr.hpp"
class base {
public:
virtual ~base() {}
};
class derived : public base {
};
int main() {
using namespace boost::lambda;
typedef boost::shared_ptr<base> ptr_type;
typedef std::map<std::string,ptr_type> map_type;
map_type m;
m["An object"]=ptr_type(new base);
m["Another object"]=ptr_type();
m["Yet another object"]=ptr_type(new base);
std::for_each(m.begin(),m.end(),
if_then_else(!bind(&ptr_type::get,
bind(&map_type::value_type::second,_1)),
(bind(&map_type::value_type::second,_1)=
bind(constructor<ptr_type>(),bind(new_ptr<derived>())),
var(std::cout) << "Created a new derived for \"" <<
bind(&map_type::value_type::first,_1) << "\".\n"),
var(std::cout) << "\"" <<
bind(&map_type::value_type::first,_1)
<< "\" already has a valid pointer.\n"));
m["Beware, this is slightly tricky"]=ptr_type();
std::cout << "\nHere we go again...\n";
std::for_each(m.begin(),m.end(),
if_then_else(!bind(&map_type::value_type::second,_1),
((bind(static_cast<void (ptr_type::*)(base*)>
(&ptr_type::reset<base>),
bind(&map_type::value_type::second,_1),
bind(new_ptr<base>()))),
var(std::cout) << "Created a new derived for \""
<< bind(&map_type::value_type::first,_1)
<< "\".\n"),
var(std::cout) << "\"" <<
bind(&map_type::value_type::first,_1)
<< "\" already has a valid pointer.\n"));
}
```
你都看懂了,是嗎?以防萬一有些混亂,我來解釋一下在這個例子中發生了什么。首先,這兩個 lambda 表達式做的是同一件事情。它們對 `std::map` 中的每一個當前為 null 的元素設置有效的指針。以下是程序運行的輸出:
```
"An object" already has a valid pointer.
"Another object" already has a valid pointer.
"Yet another object" already has a valid pointer.
<small class="calibre41">// 譯注:前面這三行在原書中有重復,共六行,與程序運行結果不符</small>
Here we go again...
"An object" already has a valid pointer.
"Another object" already has a valid pointer.
Created a new derived for "Beware, this is slightly tricky".
"Yet another object" already has a valid pointer.
```
輸出顯示我們設法把有效的對象放入 `map` 的每一個元素,但是這是如何辦到的呢?
兩個表達式完成了相同的工作,但各自有不同的方法。從第一個開始,我們來把這個 lambda 表達式切開來看看它是如何工作的。當然,第一部分是條件,它很普通:\[8\]
[8] 它還可以更簡單,我們即將會看到。
```
!bind(&ptr_type::get,bind(&map_type::value_type::second,_1))
```
這樣更容易看了,對嗎?從最里面的 `bind` 開始讀這個表達式,它告訴我們將綁定到成員 `map_type::value_type::second` (它是一個 `ptr_type`),然后我們再綁定成員函數 `ptr_type::get` (它返回 `shared_ptr` 的指向物),最后我們對整個表達式執行 `operator!`. 由于指針可以隱式轉換為 `bool`, 因此這是一個有效的布爾表達式。看完條件部分,我們來看 _then_-部分。
```
bind(&map_type::value_type::second,_1)=
bind(constructor<ptr_type>(),
bind(new_ptr<derived>())),
```
這里有三個 `bind` 表達式,第一個(我們從最左邊開始,因為這個表達式有一個賦值)取出成員 `map_type::value_type::second`, 它是一個智能指針。這就是我們要賦給它一個新的 `derived` 的那個值。第二和第三個表達式是嵌套的,所以我們從里向外讀。里面的 `bind` 負責堆上的一個 `derived` 實例的缺省構造,我們再在它的結果之上 `bind` 一個對 `ptr_type` (智能指針類型)的 `constructor` 的調用,然后把它的結果賦值(使用普通的賦值符)給最先的那個 `bind` 表達式。然后,我們給這個 _then_-部分再加一個表達式,打印出一個簡短的信息和元素的鍵值。
```
var(std::cout) << "Created a new derived for \"" <<
bind(&map_type::value_type::first,_1) << "\".\n")
```
最后,我們加上語句的 _else_-部分,它打印出元素的鍵值和一些文字。
```
var(std::cout) << "\"" <<
bind(&map_type::value_type::first,_1)
<< "\" already has a valid pointer.\n"));
```
把這個表達式分開來讀,很明顯它們并不是那么復雜,雖然整個 看起來很可怕。很重要的一點是,縮進和分離這些代碼可以更容易閱讀。我們可以寫出類似的表達式來完成這件工作,它與這一個版本非常不同,更難閱讀,但是它 的效率稍高一些。這里要注意的是,通常都會有好幾種方法來寫 lambda 表達式,就象其它編程問題的情況一樣。在寫代碼之前應該多想一下,因為你的選擇會影響最終結果的可讀性。作為比較,以下是我提到的另一個版本:
```
std::for_each(m.begin(),m.end(),
if_then_else(!bind(&map_type::value_type::second,_1),
((bind(static_cast<void (ptr_type::*)(base*)>
(&ptr_type::reset<base>),
bind(&map_type::value_type::second,_1),
bind(new_ptr<derived>()))),
var(std::cout) << "Created a new derived for \"" <<
bind(&map_type::value_type::first,_1) << "\".\n"),
var(std::cout) << "\"" <<
bind(&map_type::value_type::first,_1)
<< "\" already has a valid pointer.\n"));
```
這不是好的代碼,這些代碼由于類型轉換和復雜的嵌套 `bind`s 而變得混亂,與前面那個版本相比,這個版本很容易使我們偏離主要邏輯。為了弄明白它,我們再來把這個表達式切開成幾個部分。首先,我們有條件部分,它很簡單(這個表達式中沒有其它東西!);我們利用對 `shared_ptr` 的了解,它告訴我們有一個到 `bool` 的隱式轉換可用。因此我們可以去掉在前一個版本中使用的到成員函數 `get` 的 `bind` 。
```
!bind(&map_type::value_type::second,_1)
```
這個條件部分與原先的表達式一樣工作。接下來的部分是:
```
bind(static_cast<void (ptr_type::*)(base*)>
(&ptr_type::reset<base>),
bind(&map_type::value_type::second,_1),
bind(new_ptr<derived>()))
```
這真的很難分析,所以我們先避開它。我們不使用賦值,而是直接使用成員函數 `reset`, 它不僅是泛化的而且還是重載的。因此我們需要執行 `static_cast` 來告訴編譯器我們要用那個版本的 `reset` 。這里主要是 `static_cast` 讓表達式變得復雜,但是從最里面的表達式開始分析,我們就可以弄懂它。我們綁定一個調用到 `operator new`, 創建一個 `derived` 實例,然后我們把結果綁定到智能指針(通過成員 `map_type::value_type::second`), 然后再綁定到 `shared_ptr` 的成員函數 `reset`. 結果是,對元素中的智能指針調用 `reset` ,參數是一個新構造的 `derived` 實例。雖然我們在前一個例子中也完成了同樣的工作,但這個版本更難以理解。
要記住,通常有一些因素讓 lambda 表達式更容易或者更難閱讀和理解,要認真考慮這些因素并盡可能選擇容易的形式。這對于獲得這個庫所提供的能力是必要的,而且它會影響到你后面的程序員。
### 拋出及捕獲異常
我們已經來到本章的最后一節,討論 lambda 表達式中的異常處理。如果你對這個話題的反應是懷疑在 lambda 表達式中是否需要異常處理,這就和我的第一感覺一樣了。但是,這可能還不是你的想法。你寫過在處理數據的循環中執行局部異常處理的代碼嗎?是的,手寫的循 環可以避免使用 Boost.Lambda 庫,因此把異常處理放入 lambda 表達式是很自然的。
要使用 Boost.Lambda 的異常處理工具,就要包含頭文件 `"boost/lambda/exceptions.hpp"`. 我們來重用一下前面看到的類 `base` 和 `derived` ,并象我們前面做過的那樣執行 `dynamic_cast` ,不過這次我們要對引用執行轉型而不是對指針,這意味著如果失敗的話,`dynamic_cast` 將拋出一個異常。這使得這個例子比前面那個更直觀,因為我們不需要再使用 `if` 語句。
```
#include <iostream>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/casts.hpp"
#include "boost/lambda/bind.hpp"
#include "boost/lambda/exceptions.hpp"
int main() {
using namespace boost::lambda;
base* p1=new base;
base* p2=new derived;
(try_catch(
bind(&derived::do_more_stuff,ll_dynamic_cast<derived&>(*_1)),
catch_exception<std::bad_cast>(bind(&base::do_stuff,_1))))(p1);
(try_catch(
bind(&derived::do_more_stuff,
ll_dynamic_cast<derived&>(*_1)),
catch_exception<std::bad_cast>(
bind(&base::do_stuff,_1))))(p2);
}
```
這些表達式示范了把一個表達式包裝到一個對 tr`y_catch` 的調用。`try_catch` 的通常形式為:
```
try_catch(_expression_,
catch_exception<T1>(_expression_),
catch_exception<T2>(_expression_,
catch_all(_expression_))
```
在這段例子代碼中,表達式對 `derived&` 使用 `dynamic_cast`。第一個轉型由于 `p1` 指向一個 `base` 實例而失敗;第二個轉型則由于 `p2` 指向一個 `derived` 實例而成功。請留意對占位符的解引用(`*_1`)。這是必須的,因為我們是傳送指針參數給表達式的,而 `dynamic_cast` 要求的是對象或引用。如果你需要 `try_catch` 處理幾種類型的異常,就要確保把最特化的類型放在前面,就象普通的異常處理代碼一樣。\[9\]
9] 否則,一個更為通用的類型將會匹配該異常而不能查找到更為特殊的類型。具體詳情請參考你喜歡的C++書籍。
如果我們想訪問捕獲的異常,可以用一個特別的占位符,`_e`. 當然,在 `catch_all` 中不能這樣做,就象在 `catch (...)` 中沒有異常對象一樣。繼續前面的例子,我們可以打印出令 `dynamic_cast` 失敗的原因,象這樣:
```
try_catch(
bind(&derived::do_more_stuff,ll_dynamic_cast<derived&>(*_1)),
catch_exception<std::bad_cast>
(std::cout << bind(&std::exception::what,_e))))(p1);
```
在處理一個派生自 `std::exception` 的異常類型時——這是很常見的情形——你可以綁定到虛擬成員函數 `what`, 就象這里所示范的那樣。
但是有時候,你不想捕獲異常,而是想拋出一個異常。這可以通過函數 `throw_exception` 來實現。因為你需要創建一個異常對象用來拋出,所以從一個 lambda 表達式中拋出異常通常還要使用 `constructor` 。下面這個例子定義了一個異常類,`some_exception`, 它公有派生自 `std::exception`, 如果 lambda 表達式的參數為 `true` ,就創建并拋出一個異常。
```
#include <iostream>
#include <exception>
#include "boost/lambda/lambda.hpp"
#include "boost/lambda/exceptions.hpp"
#include "boost/lambda/if.hpp"
#include "boost/lambda/construct.hpp"
#include "boost/lambda/bind.hpp"
class some_exception : public std::exception {
std::string what_;
public:
some_exception(const char* what) : what_(what) {}
virtual const char* what() const throw() {
return what_.c_str();
}
virtual ~some_exception() throw() {}
};
int main() {
using namespace boost::lambda;
try {
std::cout << "Throw an exception here.\n";
(if_then(_1==true,throw_exception(
bind(constructor<some_exception>(),
constant("Somewhere, something went \
terribly wrong.")))))(make_const(true));
std::cout << "We'll never get here!\n";
}
catch(some_exception& e) {
std::cout << "Caught exception, \"" << e.what() << "\"\n";
}
}
```
運行這段程序將產生以下輸出:
```
Throw an exception here.
Caught exception, "Somewhere, something went terribly wrong."
```
最有趣的地方是拋出異常的那段代碼。
```
throw_exception(
bind(constructor<some_exception>(),
constant("Somewhere, something went \
terribly wrong."))
```
`throw_exception` 的參數是一個 lambda 表達式。這個例子中,它被創建并綁定到對 `some_exception` 構造函數的調用,我們把一個字符串作為 `what` 參數傳給該構造函數。
這就是在 Boost.Lambda 中進行異常處理的全部內容。永遠記住,要小心謹慎地使用這些工具,它們可以讓生活更輕松或更困難,這取決于你能否很好地并明智地使用它們\[10\] 。拋出并處理異常在你的 lambda 表達式中并不常見,但有些時候還是需要的。
?[10] 小心應了這句格言,"如果你只有錘子,那么所有東西看起來都象釘子"。
- 序
- 前言
- Acknowledgments
- 關于作者
- 本書的組織結構
- Boost的介紹
- 字符串及文本處理
- 數 據結構, 容器, 迭代器, 和算法
- 函數對象及高級編程
- 泛 型編程與模板元編程
- 數學及數字處理
- 輸入/輸出
- 雜項
- Part I: 通用庫
- Library 1. Smart_ptr
- Smart_ptr庫如何改進你的程序?
- 何時我們需要智能指針?
- Smart_ptr如何適應標準庫?
- scoped_ptr
- scoped_array
- shared_ptr
- shared_array
- intrusive_ptr
- weak_ptr
- Smart_ptr總結
- Library 2. Conversion
- Conversion 庫如何改進你的程序?
- polymorphic_cast
- polymorphic_downcast
- numeric_cast
- lexical_cast
- Conversion 總結
- Library 3. Utility
- Utility 庫如何改進你的程序?
- BOOST_STATIC_ASSERT
- checked_delete
- noncopyable
- addressof
- enable_if
- Utility 總結
- Library 4. Operators
- Operators庫如何改進你的程序?
- Operators
- 用法
- Operators 總結
- Library 5. Regex
- Regex庫如何改進你的程序?
- Regex 如何適用于標準庫?
- Regex
- 用法
- Regex 總結
- Part II: 容器及數據結構
- Library 6. Any
- Any 庫如何改進你的程序?
- Any 如何適用于標準庫?
- Any
- 用法
- Any 總結
- Library 7. Variant
- Variant 庫如何改進你的程序?
- Variant 如何適用于標準庫?
- Variant
- 用法
- Variant 總結
- Library 8. Tuple
- Tuple 庫如何改進你的程序?
- Tuple 庫如何適用于標準庫?
- Tuple
- 用法
- Tuple 總結
- Part III: 函數對象與高級編程
- Library 9. Bind
- Bind 庫如何改進你的程序?
- Bind 如何適用于標準庫?
- Bind
- 用法
- Bind 總結
- Library 10. Lambda
- Lambda 庫如何改進你的程序?
- Lambda 如何適用于標準庫?
- Lambda
- 用法
- Lambda 總結
- Library 11. Function
- Function 庫如何改進你的程序?
- Function 如何適用于標準庫?
- Function
- 用 法
- Function 總結
- Library 12. Signals
- Signals 庫如何改進你的程序?
- Signals 如何適用于標準庫?
- Signals
- 用法
- Signals 總結