這篇文章是我學習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元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

