一. 什么是Lambda dSP~R
所谓Lambda,简单的说就是快速的小函数生成。 <i$ud&D
在C++中,STL的很多算法都要求使用者提供一个函数对象。例如for_each函数,会要求用户提供一个表明“行为”的函数对象。以vector<bool>为例,如果想使用for_each对其中的各元素全部赋值为true,一般需要这么一个函数对象, ):L ; P)
Cu $mb}@
P}re"<MD
RxB9c(s^@
class filler AZ7m=Q97
{ CG0jZB#u
public : dI%#cf1
void operator ()( bool & i) const {i = true ;} j*fs [4
} ; N?3BzI%?
>D5WAQ>b
sy(8-zbI
这样实现不但麻烦,而且不直观。而如果使用lambda,则允许用户使用一种直观和见解的方式来处理这个问题。以boost.lambda为例,刚才的问题可以这么解决: lh]Q\
#*tWhXU
- %'ys
"q]r{0
for_each(v.begin(), v.end(), _1 = true ); v?L`aj1ox
0Q{^BgW
@s%!R
那么下面,就让我们来实现一个lambda库。 9`FPV`/
7Ap==J{a
aWk1D.
w7e+~8|
二. 战前分析 mM;5UPbZ
首先要说明的是,我并没有读过boost.lambda或其他任何lambda库的代码,因此如代码有雷同,纯属巧合。 zf.&E3Sn
开始实现以前,首先要分析出大致的实现手法。先让我们来看几段使用Lambda的代码 @x[Arx^?}
[9H986=
-RBH5+SS2
for_each(v.begin(), v.end(), _1 = 1 ); G9AQIU%ii
/* --------------------------------------------- */ Mrly(*!U"@
vector < int *> vp( 10 ); ><DXT nt'x
transform(v.begin(), v.end(), vp.begin(), & _1); 5 `A^"}0
/* --------------------------------------------- */ /DE`>eJY
sort(vp.begin(), vp.end(), * _1 > * _2); ;-"'sEu}
/* --------------------------------------------- */ |HfN<4NL
int b = * find_if(v.begin, v.end(), _1 >= 3 && _1 < 5 ); PCX X[N
/* --------------------------------------------- */ EF5:$#
for_each(vp.begin(), vp.end(), cout << * _1 << ' \n ' ); @q++eGm\Q
/* --------------------------------------------- */ Xo/0lT
for_each(vp.begin(), vp.end(), cout << constant( ' \n ' ) << * _1); jA,|.P>
h/xV;oj
hMQh?sF/
!K8V":1du#
看了之后,我们可以思考一些问题: "]kq,j^]
1._1, _2是什么? .]y"04@]
显然_1和_2都满足C++对于标识符的要求,可见_1和_2都是对象。 7 P/1'f3
2._1 = 1是在做什么? D&/~lhyNZ
既然_1是一个对象,那么_1的类必然重载了operator=(int)。那么operator=返回什么呢?该函数所返回的对象被传入for_each的第3个参数,可见其返回了一个函数对象。现在整个流程就很清楚了。_1 = 1调用了operator=,其返回了一个函数对象,该函数对象能够将参数1赋值为1。 2&KM&NX~
Ok,回答了这两个问题之后,我们的思路就很清晰了。如果要实现operator=,那么至少要实现2个类,一个用于产生_1的对象,另一个用于代表operator=返回的函数对象。 ~Blsj9a2
" ,>,t_J
m5kt
O^EU
三. 动工 %WO4uOi:@
首先实现一个能够范型的进行赋值的函数对象类: GSa U:A
jrLV \(p
hw)#TEt
O]-s(8Oo3
template < typename T > ^w+)A;?W
class assignment "iPX>{'En
{ [`_-;/Gx2
T value; $I4:g.gKpG
public : i564<1`x
assignment( const T & v) : value(v) {} WEsX+okj
template < typename T2 > 54>gr1B
T2 & operator ()(T2 & rhs) const { return rhs = value; } NQIbav^5
} ; *IIuGtS
JGQ)/(
A&L2&ofV&q
其中operator()被声明为模版函数以支持不同类型之间的赋值。 zCI.^^<?
然后我们就可以书写_1的类来返回assignment ~%QI#s?|
g![?P"i^t
tqzr+
g-DFcwO,V
class holder ChRCsu~
{ 8om)A0S
public : 37 d-!
template < typename T > S8dfe~ |7:
assignment < T > operator = ( const T & t) const G<-KwGy,D
{ 4)tY6ds)r|
return assignment < T > (t); t;VMtIW+E
} #Mn?Nn
} ; T90O.]S
e8> X5
,Ohhl`q(
由于该类是一个空类,因此我们可以在其后放心大胆的写上: 'uL$j=vB
-@>]iBl
static holder _1; C;HEvq7
Ok,现在一个最简单的lambda就完工了。你可以写 ,H"}Rw
P_0X+Tz
for_each(v.begin(), v.end(), _1 = 1 ); "r@#3T$
而不用手动写一个函数对象。 d^RcJ3w
A4@z+ebb l
#Y7iJPO
a. D cmy{
四. 问题分析 +BtLd+)R
虽然基本上一个Lambda已经初步实现出来了,但是仔细想想,问题也是很多的。 6/L[`n"G
1, 我们现在是把_1和functor看成两个不同的存在,会导致代码的重复。 seK;TQ3/7
2, 目前这个Lambda还无法实现如_1 = 2 = 3这样的链式操作。 \Qah*1
3, 我们没有设计好如何处理多个参数的functor。 yOlVS@7
下面我们可以对这几个问题进行分析。 liPrxuP`
8/R$}b><
五. 问题1:一致性 {ObY1Y`ea
首先来看看1,合并_1和functor的最佳方法就是把_1本身也变成functor。那么_1的operator()会做什么事情呢?| n]#YL4j
很明显,_1的operator()仅仅应该返回传进来的参数本身。 Pzzzv^+
6/wC StZ
struct holder dLH@,EKl)
{ t'$_3ml
// .$cX:"_Mk
template < typename T > ?SYmsaSr5
T & operator ()( const T & r) const 8v4krz<Iq
{ N~v<8vJq`
return (T & )r; sYXLVJ>b
} <ndY6n3
} ; +76ao7d.
$ bMmyDw
这样的话assignment也必须相应改动: y{(Dv}
09vVCM;DY
template < typename Left, typename Right > 3+'vNc
class assignment <bW~!lv
{ 8hww({S2
Left l; )!AH0p
Right r; |4/rVj"
public : 4s|qxCks
assignment( const Left & l, const Right & r) : l(l), r(r) {} 1
Lg {l
template < typename T2 > u{uqK7]+
T2 & operator ()(T2 & rhs) const { return l(rhs) = r; } e`LvHU_0
} ; E]vox~xK>
v\9f 8|K
同时,holder的operator=也需要改动: T"H"m4{'
NoF|j57?u'
template < typename T > T-4dD
assignment < holder, T > operator = ( const T & t) const %=K [C
{ N?rE:0SJ
return assignment < holder, T > ( * this , t); VPO~veQ
} @||nd,i`n~
_
j'm2BAO
好,这样holder也成为了一个functor,这为我们以后添加功能节省了很多代码。 WaX!y$/z
你可能也注意到,常数和functor地位也不平等。 ;=P!fvHk
{ **W7\h
return l(rhs) = r; x5g&?2[
在这一句中,r没有调用operator()而l调用了。这样以后就要不时的区分常数和functor,是不良的设计。 O`_, _
那么我们仿造holder的做法实现一个常数类: @[\zO'|
a)S6Z
template < typename Tp > By[M|4a
class constant_t Vy/G-IASb
{ @R;k@b
const Tp t; _I,GH{lh I
public : 7$0bgWi
constant_t( const Tp & t) : t(t) {} _ A{F2M
template < typename T > T_wh)B4xW
const Tp & operator ()( const T & r) const lOb(XH9
{ @!ja/Y^
return t; 'Zex/:QS
} B )\;Ja
} ; NTK9`#SA
%)d7iT~M
该functor的operator()无视参数,直接返回内部所存储的常数。 BSr#;;\
下面就可以修改holder的operator=了 %ib7)8Ki0
yP` K [/
template < typename T > .*+jD^Gr
assignment < holder, constant_t < T > > operator = ( const T & t) const g&.OJ
{ )L":I
return assignment < holder, constant_t < T > > ( * this , constant_t < T > (t)); 5D7k[+6
} |{,c2Ck:N
|RXXj [z
同时也要修改assignment的operator() ;/ |tU
o$
y^Q);siSy
template < typename T2 > >s.y1Vg~C
T2 & operator ()(T2 & rhs) const { return l(rhs) = r(rhs); } d mTZEO
现在代码看起来就很一致了。 -~_|ZnuM9
i>-#QKqJ
六. 问题2:链式操作 #tw_`yh
现在让我们来看看如何处理链式操作。 ]j*uD317
其实问题1已经为我们处理掉了大量的问题。如果_1,functor,常量彼此之间不统一为functor,那么链式操作的时候就要时刻小心一个对象是_1还是functor还是常量,会大大增加编码的难度。 d}|z+D
事实上,首先要解决的是,如何知道一个functor的operator()的返回值的类型。遗憾的是,我并没有找到非常自动的办法,因此我们得让functor自己来告诉我们返回值的类型。 O^ &m
比较麻烦的是,operator()的返回值一般和其参数的类型相关,而operator()通常是一个模版函数,因此其返回值类型并不能用一个简单的typedef来指定,而必须实现一个trait。 k3Puq1H
现在我们在assignment内部声明一个nested-struct &?xtmg<d
S$40nM
template < typename T > #_IuB) qy
struct result_1 *.1#+h/]3
{ KCp9P2kv.
typedef typename ref < typename Left::result_1 < T > ::result > ::reference result; VH+3o?nrT
} ; p)m5|GH24
|-cXb.M[
那么如果参数为T,其返回值类型就为result_1<T>::result。上面代码的ref<T>为一个类型转换类,作用是返回T的引用。不直接加上&符号的原因是如果T本身就是Q的引用Q&,那么Q&&是非法的。因此ref的实现即为: Oi@|4mo
79o=HiOF99
template < typename T > zITxJx
struct ref s bR*[2
{ ofI,[z3
typedef T & reference; ]HXHz(?;F
} ; SL+n y(y
template < typename T > 4?
rEO(SZ
struct ref < T &> bJ6H6D>
{ @~5Fcfmm
typedef T & reference; '*@=SM
} ; @`^Z5n.4
6g#E/{kQw
有了result_1之后,就可以把operator()改写一下: /L|x3RHs
+9yMtR
template < typename T > eIf-7S]m
typename result_1 < T > ::result operator ()( const T & t) const \CS4aIp
{ 3Q7PY46
return l(t) = r(t); pbwOma2
} mjk<FXW
可能大家已经注意到我定义assignment的operator()的返回类型的时候,是直接将其定义为Left的operator()返回类型的引用形式,如果实际上处理的对象的operator=并不是按照常理来声明的,那么这段代码可能就编译不过。这的确是一个很麻烦的事情。实际上,在gcc下,使用typeof关键字可以很容易的得到该类型的operator=的返回类型,就可以让这段代码变得更有通用性。然而为了实现可移植性,我不得不放弃这个诱人的想法。 9JILK9mVO
同理我们可以给constant_t和holder加上这个result_1。 @$%.iQ7A;
^'+#BPo9@
有了这个result_1,链式操作就简单多了。现在唯一要做的事情就是让所有的functor都重载各种操作符以产生新的functor。假设我们有add和divide两个类,那么 %t74*cX
_1 / 3 + 5会出现的构造方式是: ^4Se=Hr
z2
_1 / 3调用holder的operator/ 返回一个divide的对象 F/:%YR;
+5 调用divide的对象返回一个add对象。 ^AXH}g
最后的布局是: PTXS8e4
Add G<`(d@g
/ \ p`52
Divide 5 D+q z`
/ \ ,u-9e4
_1 3 a6It1%a+
似乎一切都解决了?不。 ?hURNlR_Q
你可以想象一下一个完整的Lambda库,它必然能够重载C++几乎所有的操作符。假设其重载了10个操作符,那么至少会有10个代表这些操作符的functor类。大体上来讲,每一种操作符所对应的functor都应当能够由链式操作产生别的任意一种操作符所对应的functor。(例如:*_1 = 2既是由operator*的functor产生operator=的functor)。可想而知这样一共能产生10*10=100种产生方式。这是对编码的一个大挑战。 i 2[8^o`_
如何简化这个问题呢?我们不妨假定,任意一种操作符的functor,都能够产生任意一种操作符的functor,这样,每一种操作符的functor都拥有一样的产生方案。如果某种转换确实是不合法的(例如:A/B=C无论如何也不可能合法),那么在试图产生新functor的时候会出现编译错误。幸好C++的模版是如果不使用就不编译的,因此这种编译错误不会干扰到正常的使用,这正是我们所要的。 xnw' &E
OK,我们的方法呼之欲出了。既然所有的functor都具有一样的产生方案,那么不如大家都不要实现,等到最后统一的在所有的functor里面加上这么一系列的产生代码吧。例如,如果要添加从某functor XXX到operator=的functor的产生代码: t47;X}y f
Bw[#,_
template < typename Right > "s t+2#{
assignment < XXX, typename picker_maker < Right > ::result > operator = ( const {CTJX2&
Right & rt) const >-w#&T &K
{ |}X[Yg=FG
return assignment < XXX, typename picker_maker < Right > ::result > ( * this , rt); OW!y7
} Uw3wR!:
下面对该代码的一些细节方面作一些解释 [?$tu%Q(Z
XXX指的是原来的functor的类型,picker_maker<T>是一个类型变换的trait,如果T是一个常量,那么他会返回constant_t<T>,否则返回T本身。 K]dR%j
因此如果该函数声明在assignment的内部,那么就实现了连等,如果声明在的dereference(解引用)的内部,就允许(*A = B)的行为发生。 AOV{@b(
最后,如何把这些函数塞到各个functor的声明里边呢?当然可以用宏,但是。。。大家都知道这样不好。 8"V1h72vcW
除了宏之外还可以用的方式就是继承。我们可以写一个类叫做picker,该类实现了所有的如上的产生函数。然后让所有的functor继承自它。 fZnq5rTk"
且慢,也许立刻就有人跳出来说:这样的话那个XXX怎么写呢?这样不是会导致循环依赖么?这样不是会有downcast么? U5?QneK
正解,让picker做基类确实不是一个好主意。反过来,让picker继承functor却是一个不错的方法。下面是picker的声明: P9q=tC3^
Y\4B2:Qd9
template < class Action > Q0ba;KPm
class picker : public Action yGX5\PSo
{ >5Y.
public : iYlkc
picker( const Action & act) : Action(act) {} 2zX9c<S=5
// all the operator overloaded -<" ;|v4
} ; ;U y}(
o
:j'd
Picker<T>继承自T,唯一的作用就是给T添加上了各种操作符的重载函数。 w BoP&l
现在所有参与行动的functor都要套上一层picker, _1被声明为 picker<holder>, 并且holder中所重载的操作符除了operator()之外全部被移到了picker内。而picker中的操作符重载的返回的functor也必须套上一个picker: <y#@v G
anORoK.
template < typename Right > M[e^Z}w.V
picker < assignment < Action, typename picker_maker < Right > ::result > > operator = ( const Right & rt) const |lyspD
{ mqY=N~/O
return assignment < Action, typename picker_maker < Right > ::result > ( * this , rt); KbH|'/w
} G{zxP%[E
G\(*z4@Gz
Piker_maker返回的也是picker<T>,或者picker<constant_t<T> > o<5+v^mt#
使用picker还带来一个额外的好处。之前提到picker_maker要区分functor和常量,有了picker,区分的方法就非常简单了:凡是属于picker<T>的都是functor,否则就是常量。 eu//Q'W
pezfB{x?
template < typename T > struct picker_maker TeyFq0j@'
{ }}Gkipp
typedef picker < constant_t < T > > result; XRP+0=0
} ; 9j5B(_J^
template < typename T > struct picker_maker < picker < T > > -WGlOpg0;
{ V=Z%y$1Bc
typedef picker < T > result; LCb0Kq}*/(
} ; R?J8#JPXD
QHtN_Q_F
下面总的结构就有了: FR\r/+n:t0
functor专心模拟操作符的行为,并实现一个result_1来告诉别人自己的返回类型。 BU|m{YZ$
picker专心负责操作符之间的产生关系,由它来联系操作符合functor。 ~N)(|N
picker<functor>构成了实际参与操作的对象。 ppeF,Q
至此链式操作完美实现。 1\/~>
}$* z:E
%Kh}6
七. 问题3 0;w84>M
如何使用多参数的函数对象呢?考虑_1=_2,这个functor必须接受2个参数,因此所产生的assignment对象的operator()必须能接收2个参数。 8sq0 BH
"po;[
Ia2
template < typename T1, typename T2 > %PPkT]~\
??? operator ()( const T1 & t1, const T2 & t2) const <<9|*Tz
{ M -8d*#_P
return lt(t1, t2) = rt(t1, t2); "J`#
} +hs:W'`%
}t1 q5@QU
很明显,这个函数的返回类型会依赖于T1,T2,因此result_1已经无法适用,我们就只好再写一个result_2: TnuA uui*
iZ(JwY
template < typename T1, typename T2 >
`Y,Rk
struct result_2 5{-54mwo
{ B.);Ju
typedef typename ref < typename Left::result_2 < T1, T2 > ::result > ::reference result; }x_:v!G
} ; _#
&_`bZH
6f+@@=Xc
显然,各个functor似乎根本不理会各个参数那个是_1, 那个是_2, 那么最后是怎么选择的呢? tNAmA
这个差事就留给了holder自己。 V)ITk\
|w>d]eA5
&7eN
EA
template < int Order > GMpg+rK
class holder; UUvR>5@n
template <> [9yy<Z5
class holder < 1 > .8wF>
8
{ mlz|KI~\F;
public : pQ8f$I#v
template < typename T > Krr51`hZH
struct result_1
O44Fj)
{ )0=H)k0
typedef T & result; QjTs$#eMW
} ; u\f QaQV
template < typename T1, typename T2 > V7k!;0u
v
struct result_2 (Y"./BDY
{ Ls&-8
typedef T1 & result; 1W7ClT_cQ
} ; T5_rPz
template < typename T > Zy>y7O(,
typename result_1 < T > ::result operator ()( const T & r) const c >xHaA:V
{ u>V~:q\X
return (T & )r; Bey9P)_Of
} I0_>ryA
template < typename T1, typename T2 > 2HDWlUTNVO
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const eqyUI|e
{ T t$]
[
return (T1 & )r1; zOis}$GR
} mD,fxm{G
} ; y!~ }7=
D#d/?\2
template <> Z5"5Ge-M
class holder < 2 > O
2+taB
{ k%;oc$0G-3
public : N\fj[?f[
template < typename T > J #;|P-pt
struct result_1 id>2G
%Tx
{ *~zB {
typedef T & result; )p{,5"0u
} ; (?[%u0%_
template < typename T1, typename T2 > ZUl-&P_X
struct result_2 TjjR% 3
{ 8,)<,g-/=
typedef T2 & result; bDq<]h_7
} ; Lv>O BHD
template < typename T > 4!-/m7%eF
typename result_1 < T > ::result operator ()( const T & r) const |Pv)&'B"
{ H2yPVJ\Y)"
return (T & )r; Ru$%gh>v
} <lRjh7
template < typename T1, typename T2 > zOYG`:/'
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const JcC2Zn6
{ Fh}GJE
return (T2 & )r2; ;S'1fci6
} OZ&SxR%q4
} ; 6h1pPx7zU
-(zw80@&
~NIqO4 D
新的holder变成了holder<int>, holder<n>的n个参数的operator()会返回第n个参数的值。而_1,_2也相应变为picker<holder<1> >, picker<holder<2> >。 FaKZ|~Y
e
现在让我们来看看(_1 = _2)(i. j)是怎么调用的: -=qHwcId
首先 assignment::operator(int, int)被调用: U[hokwZ
!j9(%,PR
return l(i, j) = r(i, j); *56q4\1
先后调用holder<1>::operator()(int, int)和holder<2>::operator()(int, int) M`?ATmYy
MTn}]blH
return ( int & )i; ob9=/ R?i
return ( int & )j; SOluTFxUw
最后执行i = j; 27Vx<W
可见,参数被正确的选择了。 07>D G#
%z-n2%
.+~9
vH
w^[:wzF0
CUj$ <ay=
八. 中期总结 1|$J>
目前的结果是这样的,为了支持一个操作符,我们需要作如下几件事: vQ>8>V
1。 实现一个functor,该functor的operator()要能执行该操作符的语义 G~/*!?&z
2。 在该functor中实现result_1至result_n,其中n是支持参数的最大值。 xN6}4JB
3。 在picker中实现一个操作符重载,返回该functor }*7Gq
}S}%4c>
J!yK/*sO,
)ZpMB
c&"1Z/tR
6df`]sc
九. 简化 a,e;(/#\7
很明显,要支持一个操作符所要做的工作太多了,而且在每个functor中申明result_1至result_n,可见如果n发生变化,维护的开销极大。 Vr #o]v
我们现在需要找到一个自动生成这种functor的方法。 b6'ZVB
首先,我们注意到result_x的形式很统一。对于各种操作符,其返回值无非下列几种: PrF('PH7i
1. 返回值。如果本身为引用,就去掉引用。 6ciA|J'MR
+-*/&|^等 F /"lJ/I
2. 返回引用。 +B4 i,]lCx
=,各种复合赋值等 #/)U0IR)
3. 返回固定类型。 Ee=!bv(%70
各种逻辑/比较操作符(返回bool) 4r9AU mJqw
4. 原样返回。 FmQiy+.|
operator, +>:[irf
5. 返回解引用的类型。 U \jFB*U
operator*(单目) gKRlXVS
6. 返回地址。 DOq"=R+
operator&(单目) Nb\B*=4AR
7. 下表访问返回类型。 y~wN:
operator[] UhSaqq
8. 如果左操作数是一个stream,返回引用,否则返回值 rC6EgWt<V
operator<<和operator>> cZAf?,>u
,+FiP{`
OK,这样我们将返回值类型总结为以上8种,就可以将各种result_x从functor中剥离出来了。 #W/ATsDt
例如针对第一条,我们实现一个policy类: ]F"@+_E
2=X.$&a
template < typename Left > 'Kd-A:K2g
struct value_return !ZayN
{ 'Ge8l%p
template < typename T > D*ZswHT{y
struct result_1 (_fovV=
{ Q 2B
typedef typename const_value < typename Left::template result_1 < T > ::result_type > ::value_type result_type; 3/8<dc
} ; FMC]KXSd
=@MJEo` D
template < typename T1, typename T2 > `|4k>5k
struct result_2 ,jeC7-tX
{ Bm&6
typedef typename const_value < typename Left::template result_2 < T1, T2 > ::result_type > ::value_type result_type; `?SLp
} ; BxesoB
} ; 6Z{(.'Be
M} Mgz
iXqRX';F'}
其中const_value是一个将一个类型转为其非引用形式的trait @R ;&P