Boost Part III. 函数对象与高级编程 Library 10. Lambda 用法
讓你的函數對象可以與Boost.Lambda 一起使用
不是所有的表達式都適合使用 lambda 表達式,復雜的表達式更適合使用普通的函數對象,而且會多次重用的表達式也應該成為你代碼中的一等公民。它們應該被收集為一個可重用函數對象的庫。但是, 你也可能想把這些函數對象用在lambda 表達式中,你希望它們可以與 Lambda 一起使用;不是所有函數對象都能做到。問題是函數對象的返回類型不能象普通函數那樣被推斷出來;這是語言的固有限制。但是,有一個定義好的方法來把這個重 要的信息提供給Lambda 庫,以使得 bind 表達式更加干凈。作為這個問題的一個例子,我們看以下函數對象:
template <typename T> class add_prev {T prev_; public:add_prev() : prev_(0){}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)));這并不是什么新問題;對于在標準庫算法中使用函數對象都有同樣的問題。在標準庫中,解決的方法是增加typedefs 來表明函數對象的返回類型及參數類型。標準庫還提供了助手類來完成這件事,即類模板unary_function 和 binary_function,要讓我們的例子類add_prev 成為合適的函數對象,可以通過定義所需的 typedefs (對于一元函數對象,是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 方法有一個問題:對于泛化的調用操作符,當返回類型或參數類型依賴于模板參數時會怎么樣?或者,當存在多個重載的調用操作符時會怎么樣?由于語言支持模板的typedefs, 這些問題可以解決,但是現在不是這樣的。這就是為什么 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>::typecv_first_argument_type;typedef typenameboost::remove_cv<cv_first_argument_type>::typefirst_argument_type;public:// The first argument helps us decide the correct versiontypedef typenamesig_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,依賴于要使用哪一個重載版本的調用操作符。通過對這個模板進行全特化,來定義正確的 typedeftype。第二個要注意的部分是 sig 類,它的第一個參數(即tuple 的第二個元素)被取出,并去掉所有的 const 或 volatile 限定符,結果類型被用于實例化正確版本的sig_helper 類,后者具有正確的 typedef type.這是為我們的類定義返回類型的一種相當復雜(但是必須!)的方法,但是多數情況下,通常都只有一個版本的調用操作符;所以正確地增加嵌套sig 類是一件普通的工作。
我們的函數對象可以在 lambda 表達式中正確使用是很重要的,在需要時定義嵌套sig 類是一個好主意;它很有幫助。
附代碼:
#include <iostream> #include <algorithm> #include <vector> #include <string> #include "boost/lambda/lambda.hpp" #include "boost/lambda/bind.hpp" 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>::typecv_first_argument_type;typedef typenameboost::remove_cv<cv_first_argument_type>::typefirst_argument_type;public:// The first argument helps us decide the correct versiontypedef typenamesig_helper<first_argument_type>::type type;}; public: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;} }; template <typename T, typename Operation> void for_all(T& t, Operation Op) {std::for_each(t.begin(), t.end(), Op); } template <typename T> void print(std::vector<T>& vec) {//using namespace boost::lambda;for_all(vec, std::cout << boost::lambda::_1 << '\n'); } int main() {using namespace boost::lambda;{std::vector<std::string> vec;vec.push_back("my darling");vec.push_back("so sweet...");std::vector<double> vec2;vec2.push_back(1);vec2.push_back(2);some_function_object sfo;std::transform(vec.begin(),vec.end(),vec2.begin(),bind(var(sfo), _1));print(vec2);}{std::vector<std::string> vec;vec.push_back("my darling");vec.push_back("so sweet...");std::vector<int> vec2;vec2.push_back(1);vec2.push_back(2);some_function_object sfo;std::transform(vec2.begin(),vec2.end(),vec.begin(),bind(var(sfo), _1));print(vec);} }總結
以上是生活随笔為你收集整理的Boost Part III. 函数对象与高级编程 Library 10. Lambda 用法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: checked_delete问题: Be
- 下一篇: Boost Part III. 函数对象