這篇文章是我學(xué)習(xí)boost phoenix的總結(jié)。
序言
Phoenix是一個(gè)C++的函數(shù)式編程(function programming)庫。Phoenix的函數(shù)式編程是構(gòu)建在函數(shù)對(duì)象上的。因此,了解Phoenix,必須先從它的基礎(chǔ)函數(shù)對(duì)象上做起。
Phoenix能夠提供令人驚艷的編碼效果。我先撂一個(gè)出來,看看用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++編譯器,不需要任何額外的工具,就能實(shí)現(xiàn)這樣的效果。這是怎么回事?且看下面逐步分解。
?
在此之前,編譯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呢?因?yàn)檫@樣會(huì)帶來不可預(yù)知的問題。這是我在實(shí)踐中發(fā)現(xiàn)的。boost的宏BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY以及類似的宏,會(huì)出現(xiàn)編譯錯(cuò)誤。真實(shí)的原因是什么,沒有細(xì)致考究。
?
另外一個(gè)原因是,要防止不必要的命名污染,因?yàn)閜hoenix用了很多和boost庫沖突的名稱,這些在使用的時(shí)候,很容易造成問題。
基礎(chǔ)函數(shù)對(duì)象
values
包含頭文件:
?
#include <boost/phoenix/core.hpp>
使用命名空間:
?
?
using boost::phoenix::val;
例子
?
?
val(3)
val("Hello, World")
val(3) 生成一個(gè)包含整數(shù)3的 函數(shù)對(duì)象 。val("Hello, World")則是一個(gè)包含字符串的 函數(shù)對(duì)象 。
?
他們是函數(shù)對(duì)象,因此,你可以象函數(shù)那樣調(diào)用他們
?
std::cout << val(3)() << val("Hello World")()<<std::endl;
val(3)() 將返回值3, val("Hello World")() 將返回值"Hello World"。
?
也許,你會(huì)覺得,這簡直是多此一舉。但是,事實(shí)上,你沒有明白phoenix的真正用以。
val(3)和val("Hello World") 實(shí)際上實(shí)現(xiàn)了一個(gè)懶惰計(jì)算的功能,將對(duì)3和"Hello World"的求值,放在需要的時(shí)候。
上面的表達(dá)式,還可以寫成這樣
?
(std::cout << val(3) << val("Hello World")<<std::endl)();
括號(hào)中std::cout << .. 這一長串,實(shí)際上生成了一個(gè)函數(shù)對(duì)象,因此我們才能在需要的時(shí)候,調(diào)用這個(gè)函數(shù)對(duì)象。
?
這是val的真正威力,它讓求值推遲到需要的時(shí)候。在普通編程中,我們必須通過類和接口才能完成。
?
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相當(dāng)于 int& 和 char const*& 的調(diào)用。
?
因此,上面 (++ref(i))()的返回值是4,而且,變量i的值也將變?yōu)?.
references是phoenix的函數(shù)對(duì)象和外部變量交換數(shù)據(jù)的橋梁。
?
Arguments
還記得boost中有_1, _2, _3, ...這些東西嗎?在phoenix中有一種類似的 arg1, arg2, arg3, ...。他們有相似的作用,但是arg1 事實(shí)上是函數(shù)對(duì)象。
?
包含頭文件
?
#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;
輸出的結(jié)果是
3, "Hello world"。?
?
?
- arg1接收1個(gè)以上的參數(shù),然后返回第1個(gè)參數(shù)
- arg2接受2個(gè)以上的參數(shù),然后返回第2個(gè)參數(shù)
- arg3接受3個(gè)以上的參數(shù),然后返回第3個(gè)參數(shù)
?
依次類推。
那么,這樣的東西有什么用呢?它實(shí)際上是用來提取參數(shù)的。arg1提取第一個(gè)參數(shù),arg2提取第二個(gè)參數(shù),....
比如,我們有一個(gè)函數(shù)
?
void testArg(F f)
{
f(1,2,3);
}
...
int main()
{
testArg(std::cout<<arg1<<"-"<<arg2<<"-"<<arg3<<std::endl);
}
std::cout ... 這一長串生成了一個(gè)函數(shù)對(duì)象。arg1 ,arg2, arg3分別提取了testArg傳遞的參數(shù)1,2,3。因此,這個(gè)函數(shù)會(huì)返回"1-2-3"。如果你將arg1和arg3的位置兌換下,返回的結(jié)果將是"3-2-1"。
?
?
Lazy Operators
操作符也可以生成函數(shù)對(duì)象。
?
頭文件
?
#include <boost/phoenix/operator.hpp>
無需命名空間
?
看個(gè)例子
?
std::find_if(vec.begin(), vec.end(), arg1 %2 == 1);
find_if的功能是查找第一個(gè)符合條件的對(duì)象,然后返回。它要求最后一個(gè)參數(shù)為一個(gè)函數(shù)或者函數(shù)對(duì)象。那么 arg1 %2 == 1是一個(gè)函數(shù)對(duì)象嗎?
?
答案是肯定的!。
它一共涉及兩個(gè)操作符 %和 == 。 arg1 % 2 生成一個(gè)函數(shù)對(duì)象,新生成的函數(shù)對(duì)象在通過 == 操作符,又生成了新的對(duì)象。
它實(shí)際上就是
?
auto func1 = operator % (arg1, 2);
auto func2 = operator == (func1, 1);
最后的func2被傳遞給了find_if。
?
phoenix支持所有的操作符,包括一元操作符在內(nèi),
如:
?
1 << 3; // Immediately evaluated
val(1) << 3; // Lazily evaluated
支持的單目運(yùn)算符有
?
?
prefix
:
~,
!,
-,
+,
++,
--,
&
(
reference
),
*
(
dereference
)
postfix
:
++,
--
?
支持的雙目運(yùn)算符有
?
=,
[],
+=,
-=,
*=,
/=,
%=,
&=,
|=,
^=,
<<=,
>>=
+,
-,
*,
/,
%,
&,
|,
^,
<<,
>>
==,
!=,
<,
>,
<=,
>=
&&,
||,
->*
?
三目運(yùn)算符
?
if_else
(
c
,
a
,
b
)
?
支持成員函數(shù)指針操作
?
struct
A
{
int
member
;
};
A
*
a
=
new
A
;
...
(
arg1
->*&
A
::
member
)(
a
);
// returns member a->member
arg1->*&A::member實(shí)現(xiàn)一個(gè)對(duì)A對(duì)象的訪問操作。
?
?
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++的語法。這個(gè)里面也充斥了函數(shù)對(duì)象。我們可以這樣看
?
?
if_.operator()(
operator > (arg1, 5)
) .operator[](
operator<<(
operator<<(std::cout, arg1)
, ",")
)
?
Lazy Statement還有很多類似的語法。它的目的是為了模擬C++的語法。 用C++模擬C++ !
它用逗號(hào)代替分號(hào),模擬語句序列,如
?
statement
,
statement
,
....
statement
主要,最后一條"語句"(實(shí)際上是函數(shù)對(duì)象),不能有“,”,如是這樣
?
?
statement
,
statement
,
statement
,
// ERROR!
就錯(cuò)了。
?
可以用括號(hào)來擴(kuò)住一些語句(即函數(shù)對(duì)象)
?
statement
,
statement
,
(
statement
,
statement
),
statement
括號(hào)也可以用在最外層,將語句(即函數(shù)對(duì)象)進(jìn)行分組,如
?
?
std
::
for_each
(
c
.
begin
(),
c
.
end
(),
(
do_this
(
arg1
),
do_that
(
arg1
)
)
);
?
?
Construct, New, Delete, Casts
可以重載類的這些實(shí)現(xiàn):
?
?
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*
?
?
函數(shù)適配器
頭文件
?
#include <boost/phoenix/function.hpp>
命名空間
?
?
boost::phoenix::function
?
?
函數(shù)對(duì)象包裝
?
考慮一個(gè)factorial函數(shù)
?
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);
}
};
解析一個(gè)這個(gè)實(shí)現(xiàn):
?
factorial_impl的result聲明是必須的,這是phoenix的模板要求的。result的聲明使用了半實(shí)例化模板
?
template <typename Sig>
struct result;
這是聲明一個(gè)主模板,當(dāng)然,主模板沒有任何用處,因此只聲明不定義。
?
?
template <typename This, typename Arg>
struct result<This(Arg &)>
{
typedef Arg type;
};
這是一個(gè)半實(shí)例化的模板。從 result<This(Arg&)>可以看出。 This(Arg&)聲明一個(gè)返回對(duì)象為 This, 參數(shù)為Arg& 的函數(shù)。
?
后面,Arg operator()(Arg)就是函數(shù)對(duì)象的實(shí)現(xiàn)體了。
使用時(shí),需要這樣
?
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;
}
?
?
適配函數(shù)宏
?
上面的代碼,書寫起來,還是比較麻煩的,因此,phoniex提供了幾個(gè)宏,用于幫助實(shí)現(xiàn)函數(shù)對(duì)象的適配。
針對(duì)普通函數(shù)的宏
?
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表明是沒有參數(shù)的。
?
針對(duì)NULLARY的例子:
聲明函數(shù):
?
namespace demo
{
int foo()
{
return 42;
}
}
生成函數(shù)對(duì)象
?
?
BOOST_PHOENIX_ADAPT_FUNCTION_NULLARY(int, foo, demo::foo)
使用它:
?
?
std::cout << "foo()():"<<foo()() << std::endl;
foo() 返回一個(gè)函數(shù)對(duì)象。 foo是一個(gè)函數(shù),你可以認(rèn)為是函數(shù)對(duì)象的工廠。
帶參數(shù)的例子
?
?
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) 生成一個(gè)函數(shù)對(duì)象,這個(gè)函數(shù)對(duì)象接收兩個(gè)整數(shù)參數(shù)。
?
至于細(xì)節(jié),了解不是很多,不管怎么樣,用就是了。
針對(duì)函數(shù)對(duì)象的宏
?
BOOST_PHOENIX_ADAPT_CALLABLE_NULLARY BOOST_PHOENIX_ADAPT_CALLABLE
?
在使用上,同F(xiàn)UNCTION對(duì)應(yīng)的函數(shù),但是,它是針對(duì)函數(shù)對(duì)象的。
如
?
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; 的聲明是必須的,因?yàn)椋莗honix模板要求的,一旦沒有,就會(huì)出錯(cuò)。
帶有重載的例子
?
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的聲明也是使用了半實(shí)例化的技巧。需要給出參數(shù)個(gè)數(shù),這個(gè)是很重要的。
?
語句
語句在上面提到過,這里介紹更多的語句
?
if_else_ 語句
?
我們開頭看到的,就是一個(gè)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_最終生成了一個(gè)函數(shù)對(duì)象,它還有一個(gè).else_對(duì)象,這個(gè)對(duì)象也是一個(gè)函數(shù)對(duì)象,可以接收任何函數(shù)對(duì)象。于是,這樣就被層層包含起來,形成了上面的奇觀。
?
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_后面是不加","的。
while_ 語句
int value;
std::for_each(vec.begin(), vec.end(),
(
ref(value) = arg1,
while_(ref(value)--)
[
std::cout<<ref(value)<<","
],
std::cout << val("\n")
)
);
我用了ref(value)來作為臨時(shí)變量。
for_ 語句
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")
)
);
無語了。
其他語句
?
總結(jié)
以上的介紹是淺嘗輒止,phoenix還有很多高級(jí)的東西未曾涉及,有興趣的讀者可以看boost相關(guān)內(nèi)容。
phoenix讓我重新認(rèn)識(shí)了C++的模板。C++的模板是C++元編程的重要利器。它甚至一定程度上改變了C++語言的語法。
不過,個(gè)人覺得,phoenix也有些過度設(shè)計(jì)。其實(shí),語句部分,可以通過編寫專門的函數(shù)來實(shí)現(xiàn)。這對(duì)大多數(shù)人來說,也就是多敲幾行代碼的問題。
我覺得有價(jià)值的是phoenix對(duì)函數(shù)的包裝,使得C++的函數(shù)具備了懶計(jì)算的能力。
懶計(jì)算避免了我們定義N多接口,以及和N多接口配合的N^N的類工廠和派生類。
使用FP編程,不必像OO編程那樣,設(shè)計(jì)者為了保證接口的兼容性,絞盡腦汁的設(shè)計(jì)接口;使用者不必為了實(shí)現(xiàn)一個(gè)簡單的功能,派生一大堆類,和一大堆工廠。
設(shè)計(jì)者根據(jù)需要,要求傳遞函數(shù)對(duì)象即可;使用者只需要包裝一個(gè)自己的實(shí)現(xiàn)給它使用,一切都搞定了。
?
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

