這篇文章是我學習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 生成一個函數對象,新生成的函數對象在通過 == 操作符,又生成了新的對象。
它實際上就是
?
auto func1 = operator % (arg1, 2); auto func2 = operator == (func1, 1);
最后的func2被傳遞給了find_if。
?
phoenix支持所有的操作符,包括一元操作符在內,
如:
?
1 << 3; // Immediately evaluated val(1) << 3; // Lazily evaluated
支持的單目運算符有
?
?
prefix : ~, !, -, +, ++, --, & ( reference ), * ( dereference ) postfix : ++, --
?
支持的雙目運算符有
?
=, [], +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>= +, -, *, /, %, &, |, ^, <<, >> ==, !=, <, >, <=, >= &&, ||, ->*
?
三目運算符
?
if_else ( c , a , b )
?
支持成員函數指針操作
?
struct A { int member ; }; A * a = new A ; ... ( arg1 ->*& A :: member )( a ); // returns member a->member
arg1->*&A::member實現一個對A對象的訪問操作。
?
?
Lazy Statements
懶惰語句。
?
頭文件
?
#include <boost/phoenix/statement.hpp>
命名空間
?
?
using boost::phoenix::if_; using boost::phoenix::switch_; using boost::phoenix::case_; using boost::phoenix::while_; using boost::phoenix::for_; ....
我們看看if_的例子
?
?
std::for_each(vec.begin(), vec.end(), if_(arg1 > 5) [ std::cout << arg1 << "," ] );
雖然看起來很奇怪,但是,它的確是C++的語法。這個里面也充斥了函數對象。我們可以這樣看
?
?
if_.operator()( operator > (arg1, 5) ) .operator[]( operator<<( operator<<(std::cout, arg1) , ",") )
?
Lazy Statement還有很多類似的語法。它的目的是為了模擬C++的語法。 用C++模擬C++ !
它用逗號代替分號,模擬語句序列,如
?
statement , statement , .... statement
主要,最后一條"語句"(實際上是函數對象),不能有“,”,如是這樣
?
?
statement , statement , statement , // ERROR!
就錯了。
?
可以用括號來擴住一些語句(即函數對象)
?
statement , statement , ( statement , statement ), statement
括號也可以用在最外層,將語句(即函數對象)進行分組,如
?
?
std :: for_each ( c . begin (), c . end (), ( do_this ( arg1 ), do_that ( arg1 ) ) );
?
?
Construct, New, Delete, Casts
可以重載類的這些實現:
?
?
construct < std :: string >( arg1 , arg2 ) // constructs a std::string from arg1, arg2 new_ < std :: string >( arg1 , arg2 ) // makes a new std::string from arg1, arg2 delete_ ( arg1 ) // deletes arg1 (assumed to be a pointer) static_cast_ < int *>( arg1 ) // static_cast's arg1 to an int*
?
?
函數適配器
頭文件
?
#include <boost/phoenix/function.hpp>
命名空間
?
?
boost::phoenix::function
?
?
函數對象包裝
?
考慮一個factorial函數
?
struct factorial_impl { template <typename Sig> struct result; template <typename This, typename Arg> struct result<This(Arg)> : result<This(Arg const &)> {}; template <typename This, typename Arg> struct result<This(Arg &)> { typedef Arg type; }; template <typename Arg> Arg operator()(Arg n) const { return (n <= 0) ? 1 : n * this->operator()(n-1); } };
解析一個這個實現:
?
factorial_impl的result聲明是必須的,這是phoenix的模板要求的。result的聲明使用了半實例化模板
?
template <typename Sig> struct result;
這是聲明一個主模板,當然,主模板沒有任何用處,因此只聲明不定義。
?
?
template <typename This, typename Arg> struct result<This(Arg &)> { typedef Arg type; };
這是一個半實例化的模板。從 result<This(Arg&)>可以看出。 This(Arg&)聲明一個返回對象為 This, 參數為Arg& 的函數。
?
后面,Arg operator()(Arg)就是函數對象的實現體了。
使用時,需要這樣
?
int main() { using boost::phoenix::arg_names::arg1; boost::phoenix::function<factorial_impl> factorial; int i = 4; std::cout << factorial(i)() << std::endl; std::cout << factorial(arg1)(i) << std::endl; return 0; }
?
?
適配函數宏
?
上面的代碼,書寫起來,還是比較麻煩的,因此,phoniex提供了幾個宏,用于幫助實現函數對象的適配。
針對普通函數的宏
?
BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY BOOST_PHOENIX_ADAPT_FUNCTION
?
它的語法是
?
BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY ( RETURN_TYPE , LAZY_FUNCTION , FUNCTION )
BOOST_PHOENIX_ADAPT_FUNCTION ( RETURN_TYPE , LAZY_FUNCTION , FUNCTION , FUNCTION_ARITY )
NULLARY表明是沒有參數的。
?
針對NULLARY的例子:
聲明函數:
?
namespace demo { int foo() { return 42; } }
生成函數對象
?
?
BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY(int, foo, demo::foo)
使用它:
?
?
std::cout << "foo()():"<<foo()() << std::endl;
foo() 返回一個函數對象。 foo是一個函數,你可以認為是函數對象的工廠。
帶參數的例子
?
?
namespace demo { int plus(int a, int b) { return a+b; } template<typename T> T plus ( T a, T b, T c) { return a + b + c; } } BOOST_PHOENIX_ADAPT_FUNCTION(int, myplus, demo::plus, 2) BOOST_PHOENIX_ADAPT_FUNCTION( typename boost::remove_reference<A0>::type , myplus , demo::plus , 3 )
這樣使用
?
?
int a = 123; int b = 256; std::cout<<"myplus:"<<(myplus(arg1, arg2)(a, b)) << std::endl; std::cout<<"myplus<3>:"<<(myplus(arg1, arg2, 3)(a, b)) << std::endl;
myplus(arg1, arg2, 3) 生成一個函數對象,這個函數對象接收兩個整數參數。
?
至于細節,了解不是很多,不管怎么樣,用就是了。
針對函數對象的宏
?
BOOST_PHOENIX_ADAPT_CALLABLE_NULLARY BOOST_PHOENIX_ADAPT_CALLABLE
?
在使用上,同FUNCTION對應的函數,但是,它是針對函數對象的。
如
?
namespace demo { struct foo2 { typedef int result_type; int operator()() const { return 42; } }; } BOOST_PHOENIX_ADAPT_CALLABLE_NULLARY(foo2, demo::foo2)
聲明方法和BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY 幾乎是一樣的,但是它不需要給出返回值。
?
值得注意的是,foo2中 typedef int result_type; 的聲明是必須的,因為,它是phonix模板要求的,一旦沒有,就會出錯。
帶有重載的例子
?
namespace demo { struct plus { template<typename Sig> struct result; template<typename This, typename A0, typename A1> struct result<This(A0, A1)> :boost::remove_reference<A0> {}; template<typename This, typename A0, typename A1, typename A2> struct result<This(A0, A1,A2)> :boost::remove_reference<A0> {}; template<typename A0, typename A1> ? ? ? ? A0 operator()(A0 const& a0, A1 const &a1) const ? ? ? ? { ? ? ? ? ? ? return a0 + a1; ? ? ? ? } ? ? ? ? template<typename A0, typename A1, typename A2> ? ? ? ? A0 operator()(A0 const& a0, A1 const &a1, A2 const &a2) const ? ? ? ? { ? ? ? ? ? ? return a0 + a1 + a2; ? ? ? ? } ? ? }; } BOOST_PHOENIX_ADAPT_CALLABLE(plus, demo::plus, 2) BOOST_PHOENIX_ADAPT_CALLABLE(plus, demo::plus, 3)
struct result的聲明也是使用了半實例化的技巧。需要給出參數個數,這個是很重要的。
?
語句
語句在上面提到過,這里介紹更多的語句
?
if_else_ 語句
?
我們開頭看到的,就是一個if_else_語句
?
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" ] ] );
?
if_最終生成了一個函數對象,它還有一個.else_對象,這個對象也是一個函數對象,可以接收任何函數對象。于是,這樣就被層層包含起來,形成了上面的奇觀。
?
switch_ 語句
std::for_each(vec.begin(), vec.end(), switch_(arg1) [ case_<1>(std::cout<<arg1<<":"<<val("one") << "\n"), case_<2>(std::cout<<arg1<<":"<<val("two") << "\n"), default_(std::cout<<arg1<<":"<<val("other value") << "\n") ] );注意default_后面是不加","的。
int iii; std::for_each(vec.begin(), vec.end(), ( for_(ref(iii) = 0, ref(iii) < arg1, ++ ref(iii)) [ std::cout << arg1 << ", " ], std::cout << val("\n") ) );
無語了。
其他語句
?
總結
以上的介紹是淺嘗輒止,phoenix還有很多高級的東西未曾涉及,有興趣的讀者可以看boost相關內容。
phoenix讓我重新認識了C++的模板。C++的模板是C++元編程的重要利器。它甚至一定程度上改變了C++語言的語法。
不過,個人覺得,phoenix也有些過度設計。其實,語句部分,可以通過編寫專門的函數來實現。這對大多數人來說,也就是多敲幾行代碼的問題。
我覺得有價值的是phoenix對函數的包裝,使得C++的函數具備了懶計算的能力。
懶計算避免了我們定義N多接口,以及和N多接口配合的N^N的類工廠和派生類。
使用FP編程,不必像OO編程那樣,設計者為了保證接口的兼容性,絞盡腦汁的設計接口;使用者不必為了實現一個簡單的功能,派生一大堆類,和一大堆工廠。
設計者根據需要,要求傳遞函數對象即可;使用者只需要包裝一個自己的實現給它使用,一切都搞定了。
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
