boost 的函数式编程库 Phoenix入门学习

这篇文章是我学习boost phoenix的总结。


序言

Phoenix是一个C++的函数式编程(function programming)库。Phoenix的函数式编程是构建在函数对象上的。因此,了解Phoenix,必须先从它的基础函数对象上做起。

Phoenix能够提供令人惊艳的编码效果。我先撂一个出来,看看用Phoenix能写出什么样的代码:

    std::for_each(vec.begin(), vec.end(),
        if_(arg1 > 5)
        [
            std::cout << arg1 << ">5\n"
        ]
        .else_
        [
            if_(arg1 == 5)
            [
                std::cout << arg1 << "== 5\n"
            ]
            .else_
            [
                std::cout << arg1 << "< 5\n"
            ]
        ]
    );
这是C++代码?答案是肯定的!只需要C++编译器,不需要任何额外的工具,就能实现这样的效果。这是怎么回事?且看下面逐步分解。


在此之前,编译phoenix库必须

包含核心头文件

#include <boost/phoenix/core.hpp>

注意,不要使用using namespace boost::phoenix,而要直接使用using boost::phoenix::val, ....,如

using boost::phoenix::val;
using boost::phoenix::arg_names::arg1;
using boost::phoenix::arg_names::arg2;
using boost::phoenix::case_;
using boost::phoenix::ref;
using boost::phoenix::for_;
using boost::phoenix::let;
using boost::phoenix::lambda;
using boost::phoenix::local_names::_a;

为什么不要直接使用using namespace boost::phoenix呢?因为这样会带来不可预知的问题。这是我在实践中发现的。boost的宏BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY以及类似的宏,会出现编译错误。真实的原因是什么,没有细致考究。

另外一个原因是,要防止不必要的命名污染,因为phoenix用了很多和boost库冲突的名称,这些在使用的时候,很容易造成问题。


基础函数对象

values

包含头文件:

#include <boost/phoenix/core.hpp>
使用命名空间:

using boost::phoenix::val;
例子

val(3)
val("Hello, World")
val(3) 生成一个包含整数3的 函数对象。val("Hello, World")则是一个包含字符串的 函数对象

他们是函数对象,因此,你可以象函数那样调用他们

std::cout << val(3)() << val("Hello World")()<<std::endl;
val(3)() 将返回值3, val("Hello World")() 将返回值"Hello World"。

也许,你会觉得,这简直是多此一举。但是,事实上,你没有明白phoenix的真正用以。

val(3)和val("Hello World") 实际上实现了一个懒惰计算的功能,将对3和"Hello World"的求值,放在需要的时候。


上面的表达式,还可以写成这样

(std::cout << val(3) << val("Hello World")<<std::endl)();
括号中std::cout << .. 这一长串,实际上生成了一个函数对象,因此我们才能在需要的时候,调用这个函数对象。


这是val的真正威力,它让求值推迟到需要的时候。在普通编程中,我们必须通过类和接口才能完成。


References

包含头文件

#include <boost/phoenix/core.hpp>
使用命名空间

using boost::phoenix::ref;
如果声明了如下变量:

    int i = 3;
    char const* s = "Hello World";
    std::cout << (++ref(i))() << std::endl;
    std::cout << ref(s)() << std::endl;

ref与val都是可以延迟求值的,但是,不同的是,ref相当于 int& 和 char const*& 的调用。

因此,上面 (++ref(i))()的返回值是4,而且,变量i的值也将变为4.

references是phoenix的函数对象和外部变量交换数据的桥梁。

Arguments

还记得boost中有_1, _2, _3, ...这些东西吗?在phoenix中有一种类似的 arg1, arg2, arg3, ...。他们有相似的作用,但是arg1 事实上是函数对象。

包含头文件

#include <boost/phoenix/core.hpp>

使用命名空间

using boost::phoenix::arg_names::arg1;
using boost::phoenix::arg_names::arg2;
using boost::phoenix::arg_names::arg3;
....

看下面的例子

    std::cout << arg1(3) << std::endl;
    std::cout << arg2(2, "hello world") << std::endl;

输出的结果是 3, "Hello world"。 

  • arg1接收1个以上的参数,然后返回第1个参数
  • arg2接受2个以上的参数,然后返回第2个参数
  • arg3接受3个以上的参数,然后返回第3个参数

依次类推。


那么,这样的东西有什么用呢?它实际上是用来提取参数的。arg1提取第一个参数,arg2提取第二个参数,....

比如,我们有一个函数

void testArg(F f)
{
    f(1,2,3);
}

...
int main()
{
    testArg(std::cout<<arg1<<"-"<<arg2<<"-"<<arg3<<std::endl);
}
std::cout ... 这一长串生成了一个函数对象。arg1 ,arg2, arg3分别提取了testArg传递的参数1,2,3。因此,这个函数会返回"1-2-3"。如果你将arg1和arg3的位置兑换下,返回的结果将是"3-2-1"。


Lazy Operators

操作符也可以生成函数对象。

头文件

#include <boost/phoenix/operator.hpp>

无需命名空间

看个例子

std::find_if(vec.begin(), vec.end(), arg1 %2 == 1);

find_if的功能是查找第一个符合条件的对象,然后返回。它要求最后一个参数为一个函数或者函数对象。那么 arg1 %2 == 1是一个函数对象吗?

答案是肯定的!。

它一共涉及两个操作符 %和 == 。 arg1 % 2 生成一个函数对象,新生成的函数对象在通过 == 操

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值