这篇文章是我学习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个参数
依次类推。
比如,我们有一个函数
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 生成一个函数对象,新生成的函数对象在通过 == 操