让你的函数对象可以与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_functionbinary_function,要让我们的例子类add_prev 成为合适的函数对象,可以通过定义所需的 typedefs (对于一元函数对象,是argument_typeresult_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特化。这个类型可以是 intstd::string,依赖于要使用哪一个重载版本的调用操作符。通过对这个模板进行全特化,来定义正确的 typedeftype。第二个要注意的部分是 sig 类,它的第一个参数(即tuple 的第二个元素)被取出,并去掉所有的 constvolatile 限定符,结果类型被用于实例化正确版本的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 用法相关推荐

  1. Boost Part III. 函数对象与高级编程 Library 10. Lambda 用法 switch_statement

    这些就是 if-then-else, 我们再看看 switch-语句,它与标准C++ switch有些不同. (switch_statement(_1,case_statement<0>( ...

  2. Linux环境高级编程函数,Linux环境高级编程--出错处理(CLStatus)

    很多程序库对外提供若干类,每个方法出错时如何告知调用者是否出错,以及出错码(在Linux上在error.h中的全局errno就是保存我们Linux程序执行的出错码的)?方法很多,为了简化起见,函数将返 ...

  3. c#高级编程第11版 pdf网盘_c#高级编程_c#高级编程 目录 微盘_c#高级编程第10版 pdf...

    c#高级编程 C#高级编程(第9版)-C# 5.0 & .NET 4.5.1是由.NET专家的梦幻组合编写,包含开发人员使用C#所需的所有内容.C#是编写.NET应用程序的一种语言,本书适合于 ...

  4. c语言前置函数,C语言高级编程-函数前置与后置调用

    / linux gcc下测试通过(有Bug请提交) 使用本代码需要注名作者: fqheda 本代码遵循GPL V3.0标准,可免费使用-- 函数前置与后置调用 注解:在同一个.c中,一个函数A调用另一 ...

  5. fork()函数_UNIX环境高级编程(APUE)系列学习第8章-2 exit系列函数与wait系列函数...

    5 函数exit exit函数._exit函数和_Exit函数. 在7.3节中介绍了5中进程的正常终止和3种异常终止的方法. 但是无论进程如何终止,最后都会执行内核中的同一段代码.折断代码为相应进程关 ...

  6. 慕课网Flask高级编程实战-10.鱼书业务处理

    10.1 最近的礼物 我们的首页会显示最近的赠送书籍列表.这个列表有三个限制条件: 1.数量不超过30 2.按照时间倒序排列,最新的排在最前面 3.去重,同一本书籍的礼物不重复出现 1.首先编写复杂S ...

  7. c++面向对象高级编程 总目录

    本文是对学习侯捷视频 c++面向对象高级编程系列博客的目录总索引. c++面向对象高级编程 学习一 不带指针的类: 访问私有成员变量的方式,内联inline,常量成员函数,构造函数,值传递,引用传递, ...

  8. c++高级编程学习笔记5

    multimap multimap 是一种允许多个元素使用同一个键的 map.和 map 一样,multimap 支持统一初始化.multimap的接口和 map 的接口几乎相同,区别在于multim ...

  9. 请问unix高级编程中的open包含哪些用法

    "open" 在 Unix 高级编程中是指一个用于打开文件.设备和网络连接的系统调用.它的用法包括: 打开一个文件:通过 open 函数可以打开一个文件,并获取一个文件描述符. 设 ...

最新文章

  1. 系统学习Spring之Spring in action(二)
  2. OpenStack icehouse系列之计算节点搭建
  3. 新海诚没有参与制作的作品_新海诚作品不出真人版,其实都是因为这!
  4. 前端面试题及答案整理(一)
  5. 163邮箱有传真服务器,网易企业邮箱邮件传真功能使用说明
  6. B端产品经理需要着重锻炼自己这些能力
  7. dry的原理_速干面料的原理
  8. 连数据都读不懂,你凭什么说会数据分析?
  9. Ubuntu,kubuntu与xubuntu的差别 Ubuntu各版本主要差异
  10. Memcache架构新思考
  11. vue 判断权限过期_vue 路由权限
  12. leetcode *207. 课程表(拓补排序)(2020.8.4)
  13. C语言——判断一个数字是否为质数(素数)!
  14. 一文搞懂 RabbitMQ 延时队列(订单定时取消为例)
  15. 深度学习在OCR中的应用
  16. docker安装wechat微信、wxwork企业微信脚本整理
  17. 保利威视自定义右键菜单设置
  18. 格拉姆矩阵 Gram Matrix 简单理解
  19. 股票量化投资策略有哪些特点?
  20. 光伏行业报告:N型发展推动POE胶膜应用,POE产业链进入红利期

热门文章

  1. -32767转化为二进制_程序员需要了解的硬核知识之二进制
  2. asm 查看 数据文件 修改 时间_更高效的GMX分段模拟方法:修改tpr文件
  3. matlab 电气系统设计,MATLAB 简化了控制系统的设计和分析
  4. ART:分享收集的关于博物馆的视频纪录片,建议喜欢艺术人们的收藏
  5. 成功解决UnicodeDecodeError: 'utf-8' codec can't decode byte 0xce in position 130: invalid continuation b
  6. SQLServer之分离数据库
  7. 关于ML.NET v0.7的发布说明
  8. Linux系统新建用户用ssh远程登陆显示-bash-4.1$
  9. 内存不足导致mysql关闭,CentOS6.5增加swap分区
  10. 搜索打表大找规律 (hdu2045)