一. 什么是Lambda 51:NL[[6
所谓Lambda,简单的说就是快速的小函数生成。 aTJs.y-I~
在C++中,STL的很多算法都要求使用者提供一个函数对象。例如for_each函数,会要求用户提供一个表明“行为”的函数对象。以vector<bool>为例,如果想使用for_each对其中的各元素全部赋值为true,一般需要这么一个函数对象, i+`N0!8lY
)~(( 6?k4e
xp+Z%0D
(`z`ni
class filler . 4$SNzv3V
{ }jF67c->
public : Ni"M.O);t
void operator ()( bool & i) const {i = true ;} D;~c`G
"f
} ;
4d\1W?i-
:%&~/@B
'IR2H{Q
这样实现不但麻烦,而且不直观。而如果使用lambda,则允许用户使用一种直观和见解的方式来处理这个问题。以boost.lambda为例,刚才的问题可以这么解决: :i;iSrKy
e -sZ_<GH
Wn p\yx`
V/
a!&_""
for_each(v.begin(), v.end(), _1 = true ); irg%n
e;IzK]kP
XMt5o&U1
那么下面,就让我们来实现一个lambda库。 3+[R !
W<W5ih,#
#x)lN
=#tQhg,_
二. 战前分析 w 0V=49
首先要说明的是,我并没有读过boost.lambda或其他任何lambda库的代码,因此如代码有雷同,纯属巧合。 y$JM=f$
开始实现以前,首先要分析出大致的实现手法。先让我们来看几段使用Lambda的代码 W$E!}~Ro
I-=H;6w7
jrOqspv
for_each(v.begin(), v.end(), _1 = 1 ); *)+K+J
/* --------------------------------------------- */ 8OYw72&
vector < int *> vp( 10 ); 3B{B6w}t&
transform(v.begin(), v.end(), vp.begin(), & _1); V(-=@UW
/* --------------------------------------------- */
Fo$kD(
sort(vp.begin(), vp.end(), * _1 > * _2); O!Rw?
Y
/* --------------------------------------------- */ (5-4`:1ux
int b = * find_if(v.begin, v.end(), _1 >= 3 && _1 < 5 ); 5Z2tTw'i
/* --------------------------------------------- */ O@$wU9D<
for_each(vp.begin(), vp.end(), cout << * _1 << ' \n ' ); ]!v:xjzT
/* --------------------------------------------- */ @vy{Q7aM
for_each(vp.begin(), vp.end(), cout << constant( ' \n ' ) << * _1); z?9vbx
BKiyog
F_Pv\?35z
g;|3n&
看了之后,我们可以思考一些问题: _A[k&nO!&J
1._1, _2是什么? Klw\
显然_1和_2都满足C++对于标识符的要求,可见_1和_2都是对象。 jB"?iC.
2._1 = 1是在做什么? YIb=rR[ $
既然_1是一个对象,那么_1的类必然重载了operator=(int)。那么operator=返回什么呢?该函数所返回的对象被传入for_each的第3个参数,可见其返回了一个函数对象。现在整个流程就很清楚了。_1 = 1调用了operator=,其返回了一个函数对象,该函数对象能够将参数1赋值为1。 3k5C;5
Ok,回答了这两个问题之后,我们的思路就很清晰了。如果要实现operator=,那么至少要实现2个类,一个用于产生_1的对象,另一个用于代表operator=返回的函数对象。 KF'DOXBw>
dZSv=UY)
3,Dc}$t
三. 动工 Stw%OP@?
首先实现一个能够范型的进行赋值的函数对象类: 0N" VOEvG
DH3.4EUWS
:P!"'&gCL
>= G{.H
template < typename T > F@+FXnz
class assignment {
S]"-x
{ tH7@oV;
T value; 9e`.H0
public : WAzYnl'p
assignment( const T & v) : value(v) {} =.*+c\
template < typename T2 > |H!kU.f]
T2 & operator ()(T2 & rhs) const { return rhs = value; } 3bCb_Y
} ; @raw8w\Zj+
@W{VT7w
&}YJ"o[I
其中operator()被声明为模版函数以支持不同类型之间的赋值。 Py&DnG'H
然后我们就可以书写_1的类来返回assignment 'G6M:IXno
dtXAEL\q
mX4u#$xs:
Z= 'DV1A$,
class holder "ggViIOw&
{ 2HxT+|~d6
public : 88K=jo))b
template < typename T > ?1DA
assignment < T > operator = ( const T & t) const 3G4N0{i
{ -uE2h[X|
return assignment < T > (t); ??4#)n
k
} LjE@[@d
} ; U\crp
T`
aJQx"6c?
Z#J
cNquM
由于该类是一个空类,因此我们可以在其后放心大胆的写上: ~+JEl%
XAn{xNpz
static holder _1; ucVWvXCr
Ok,现在一个最简单的lambda就完工了。你可以写 qIO<\Yl
s,tZi6Z=%E
for_each(v.begin(), v.end(), _1 = 1 ); ] bPj%sb*@
而不用手动写一个函数对象。 1XwW4cZ>:
]VYv>o`2
R')D~JJ<8a
O%w"bEr)N
四. 问题分析 UG]]Vk1d]
虽然基本上一个Lambda已经初步实现出来了,但是仔细想想,问题也是很多的。 |=dmxfj@
1, 我们现在是把_1和functor看成两个不同的存在,会导致代码的重复。 d]kP@flOV
2, 目前这个Lambda还无法实现如_1 = 2 = 3这样的链式操作。 -G!W6$Y
3, 我们没有设计好如何处理多个参数的functor。 @[:JQ'R=
下面我们可以对这几个问题进行分析。 u{H'evv0O
=p1aF/1$I
五. 问题1:一致性 stb)Tl^
首先来看看1,合并_1和functor的最佳方法就是把_1本身也变成functor。那么_1的operator()会做什么事情呢?| -{ae
很明显,_1的operator()仅仅应该返回传进来的参数本身。 h{$mL#J
8 |@WuD
struct holder %lr<;
{ i?*_-NAm
// I6k S1
template < typename T > lbRm(W(
T & operator ()( const T & r) const GaD]qeS-K
{ `u. /2]n
return (T & )r; Ca&p;K9FR
} #P)7b,3pe
} ; gwf*M3(
1X5*V!u
这样的话assignment也必须相应改动: l> Mth+,b
/~4wM#Yi8
template < typename Left, typename Right > m]Sv>|
class assignment R5y+bMZ
{ v(ATbY75
Left l; GN7\p)
Right r; FMuakCic5
public : ^/)!)=?
assignment( const Left & l, const Right & r) : l(l), r(r) {} l7.W2mg
template < typename T2 > Eyv|~D
T2 & operator ()(T2 & rhs) const { return l(rhs) = r; } &TpzJcd"
} ; A3\%t@y
=:|fN3nJ2
同时,holder的operator=也需要改动: !hBzT7CO
__FhuP P
template < typename T > ;}=4z^^5
assignment < holder, T > operator = ( const T & t) const qtx5N)J6
{ C< :F<[H
return assignment < holder, T > ( * this , t); 75O-%9lFF
} S.!0~KR:U
_n[4+S*v(
好,这样holder也成为了一个functor,这为我们以后添加功能节省了很多代码。 v,\2$q/
你可能也注意到,常数和functor地位也不平等。 JOR ?xCc
*zf@J'
return l(rhs) = r; BUuU#e5
在这一句中,r没有调用operator()而l调用了。这样以后就要不时的区分常数和functor,是不良的设计。 /(aKhUjhb
那么我们仿造holder的做法实现一个常数类: dHcGe{T^(
+<^TyIJ0
template < typename Tp > ][ ,NNXrc&
class constant_t :sMc}k?9S
{ zF&>1y.$
const Tp t; 7c:5Ey
public : 6Y#V;/gK!5
constant_t( const Tp & t) : t(t) {} \Oku<5
template < typename T > ]^>#?yEA3
const Tp & operator ()( const T & r) const efK)6T^p
{ @.4e^Km
return t; L4)@lmd3
} 5]Wkk~a
} ; =,*4:TU
}]qx "
该functor的operator()无视参数,直接返回内部所存储的常数。 5`ma#_zk|f
下面就可以修改holder的operator=了 xJ;DkPh
d/Sx+1
"{T
template < typename T > W|go*+`W%
assignment < holder, constant_t < T > > operator = ( const T & t) const GM5s~,
{ ZQd\!K8y^Q
return assignment < holder, constant_t < T > > ( * this , constant_t < T > (t)); m[@7!.0=
} Rwy<#9R[x
P]Hcg|&
同时也要修改assignment的operator() STC'j1U
F-^#EkEGe
template < typename T2 > ,W'?F9Y\
T2 & operator ()(T2 & rhs) const { return l(rhs) = r(rhs); } {kLL&`ii
现在代码看起来就很一致了。 ?c vXuxCm
&DqeO8?Q
六. 问题2:链式操作 _]W
}6?i
现在让我们来看看如何处理链式操作。 {
.z6J)?J2
其实问题1已经为我们处理掉了大量的问题。如果_1,functor,常量彼此之间不统一为functor,那么链式操作的时候就要时刻小心一个对象是_1还是functor还是常量,会大大增加编码的难度。 =Yxu {]G
事实上,首先要解决的是,如何知道一个functor的operator()的返回值的类型。遗憾的是,我并没有找到非常自动的办法,因此我们得让functor自己来告诉我们返回值的类型。 e[3rz%'Q
比较麻烦的是,operator()的返回值一般和其参数的类型相关,而operator()通常是一个模版函数,因此其返回值类型并不能用一个简单的typedef来指定,而必须实现一个trait。 x*)@:W!
现在我们在assignment内部声明一个nested-struct ~(TS>ck@
;K'1dsA
template < typename T > bdn{Y
struct result_1 y=L9E?
{ H:~41f[
typedef typename ref < typename Left::result_1 < T > ::result > ::reference result; Q~5!c#r
} ; Cq7EdK;x
'xO^2m+N;
那么如果参数为T,其返回值类型就为result_1<T>::result。上面代码的ref<T>为一个类型转换类,作用是返回T的引用。不直接加上&符号的原因是如果T本身就是Q的引用Q&,那么Q&&是非法的。因此ref的实现即为: Vx]{<}(gr
94=aVM\>>
template < typename T > Z/z(P8#U\
struct ref u>G#{$)
{ 0FDfB;
typedef T & reference; a\wpJ|3{=T
} ; u1?1x
template < typename T > Ib)>M`J
struct ref < T &> Ha~g8R&
{ qlT'gUt=H
typedef T & reference; G3j&8[
} ; hRn[ 9B
i;1EXM
有了result_1之后,就可以把operator()改写一下: x5Sc+5?*
x<
Td
template < typename T > F5CV<-jB
typename result_1 < T > ::result operator ()( const T & t) const lP@/x+6tg
{ +^St"GWY
return l(t) = r(t); {9 >jWNx
} @K 8sNPK
可能大家已经注意到我定义assignment的operator()的返回类型的时候,是直接将其定义为Left的operator()返回类型的引用形式,如果实际上处理的对象的operator=并不是按照常理来声明的,那么这段代码可能就编译不过。这的确是一个很麻烦的事情。实际上,在gcc下,使用typeof关键字可以很容易的得到该类型的operator=的返回类型,就可以让这段代码变得更有通用性。然而为了实现可移植性,我不得不放弃这个诱人的想法。 @wWro?s'p
同理我们可以给constant_t和holder加上这个result_1。 J!Kk7!^|
Y.O/~ af
有了这个result_1,链式操作就简单多了。现在唯一要做的事情就是让所有的functor都重载各种操作符以产生新的functor。假设我们有add和divide两个类,那么 zSYh\g"
_1 / 3 + 5会出现的构造方式是: ZMSP8(V
_1 / 3调用holder的operator/ 返回一个divide的对象 0]dL;~0y.
+5 调用divide的对象返回一个add对象。 Kvu0Av-7
最后的布局是: kf3yJP/
Add W$x'+t5H
/ \ H3=U|wr|
Divide 5 S`LS/)
/ \ @v1f)(N
_1 3 |[k/%
似乎一切都解决了?不。 A7~~{9
你可以想象一下一个完整的Lambda库,它必然能够重载C++几乎所有的操作符。假设其重载了10个操作符,那么至少会有10个代表这些操作符的functor类。大体上来讲,每一种操作符所对应的functor都应当能够由链式操作产生别的任意一种操作符所对应的functor。(例如:*_1 = 2既是由operator*的functor产生operator=的functor)。可想而知这样一共能产生10*10=100种产生方式。这是对编码的一个大挑战。 E%CJM+r!
如何简化这个问题呢?我们不妨假定,任意一种操作符的functor,都能够产生任意一种操作符的functor,这样,每一种操作符的functor都拥有一样的产生方案。如果某种转换确实是不合法的(例如:A/B=C无论如何也不可能合法),那么在试图产生新functor的时候会出现编译错误。幸好C++的模版是如果不使用就不编译的,因此这种编译错误不会干扰到正常的使用,这正是我们所要的。 rYnjQr2a
OK,我们的方法呼之欲出了。既然所有的functor都具有一样的产生方案,那么不如大家都不要实现,等到最后统一的在所有的functor里面加上这么一系列的产生代码吧。例如,如果要添加从某functor XXX到operator=的functor的产生代码: >Q0HqOq
*mQOW]x%
template < typename Right > 3>[_2}l
assignment < XXX, typename picker_maker < Right > ::result > operator = ( const Z4\$h1tl
Right & rt) const v{ F/Bifo
{ *"N756Cj
return assignment < XXX, typename picker_maker < Right > ::result > ( * this , rt); )V!dmVQq{g
} +LwE=unS
下面对该代码的一些细节方面作一些解释 :y)'_p *l/
XXX指的是原来的functor的类型,picker_maker<T>是一个类型变换的trait,如果T是一个常量,那么他会返回constant_t<T>,否则返回T本身。 <y+8\m
因此如果该函数声明在assignment的内部,那么就实现了连等,如果声明在的dereference(解引用)的内部,就允许(*A = B)的行为发生。 :les
3T}2
最后,如何把这些函数塞到各个functor的声明里边呢?当然可以用宏,但是。。。大家都知道这样不好。 G)A5;u\P9
除了宏之外还可以用的方式就是继承。我们可以写一个类叫做picker,该类实现了所有的如上的产生函数。然后让所有的functor继承自它。 &j@i>(7
且慢,也许立刻就有人跳出来说:这样的话那个XXX怎么写呢?这样不是会导致循环依赖么?这样不是会有downcast么?
1*_wJ
正解,让picker做基类确实不是一个好主意。反过来,让picker继承functor却是一个不错的方法。下面是picker的声明: fJ[(zjk
kaxAIk8l
template < class Action > jgLCs)=5hV
class picker : public Action /aepE~T
{ gxpGi@5
public : D0?l$]aE
picker( const Action & act) : Action(act) {} 7`^]:t
// all the operator overloaded U>^u!1X
} ; N?d4Pu1m
kRBPl99
Picker<T>继承自T,唯一的作用就是给T添加上了各种操作符的重载函数。 nw3CI&Y`
现在所有参与行动的functor都要套上一层picker, _1被声明为 picker<holder>, 并且holder中所重载的操作符除了operator()之外全部被移到了picker内。而picker中的操作符重载的返回的functor也必须套上一个picker: [XA
f=x
tqY)
template < typename Right > '1{#I/P;
picker < assignment < Action, typename picker_maker < Right > ::result > > operator = ( const Right & rt) const dP(*IOO.
{ K!q:A+]
return assignment < Action, typename picker_maker < Right > ::result > ( * this , rt); hJ0)"OA5
} H26'8e
lY5a=mwHU
Piker_maker返回的也是picker<T>,或者picker<constant_t<T> > 66"-Xf~u
使用picker还带来一个额外的好处。之前提到picker_maker要区分functor和常量,有了picker,区分的方法就非常简单了:凡是属于picker<T>的都是functor,否则就是常量。 |V2+4b,
&lYZ=|6
template < typename T > struct picker_maker ~Co7 %e V
{ ;;E "+.
typedef picker < constant_t < T > > result; ;Ry
)^5Q
} ; z.f~wAT@<
template < typename T > struct picker_maker < picker < T > > 2}P<}-?6
{ 'l$<DcBj
typedef picker < T > result; Ak!l}d
} ; A&i
Z9rs,_A
下面总的结构就有了: vb{+yEa
functor专心模拟操作符的行为,并实现一个result_1来告诉别人自己的返回类型。 _
i )Z8#
picker专心负责操作符之间的产生关系,由它来联系操作符合functor。 ,Yg<Z1
picker<functor>构成了实际参与操作的对象。 U@$Kp>X
至此链式操作完美实现。 gk+$CyjJ
Az2HlKF"L
s9 '*Vm
七. 问题3 Cc:m~e6r
如何使用多参数的函数对象呢?考虑_1=_2,这个functor必须接受2个参数,因此所产生的assignment对象的operator()必须能接收2个参数。 n237%LH[
CErkmod{}e
template < typename T1, typename T2 > f!}c0nb
??? operator ()( const T1 & t1, const T2 & t2) const :%Dw3IrOM
{ h(hb?f@1:
return lt(t1, t2) = rt(t1, t2); `;L0ax
} W?m?r.K?
DXAA[hUjF
很明显,这个函数的返回类型会依赖于T1,T2,因此result_1已经无法适用,我们就只好再写一个result_2: :U`8s#
6g@@V=mf
template < typename T1, typename T2 > [{F8+a^
struct result_2 oLcOp.8h[
{ s1XW}Dw
typedef typename ref < typename Left::result_2 < T1, T2 > ::result > ::reference result; hS4Ljyeg
} ; "1rZwFI0l
JHN35a+
显然,各个functor似乎根本不理会各个参数那个是_1, 那个是_2, 那么最后是怎么选择的呢? Pm]6E[zC
这个差事就留给了holder自己。 ^'DrU<o
24 S,w>j
t@-:e^ v
template < int Order > v~:$]a8
class holder; 3\6UH
template <> T!o 4k
class holder < 1 > rt5UT~
{ /ey[cm2#[s
public : 9V&%_.Z
template < typename T > N1ZHaZ
struct result_1 Fkas*79
{ $smzP.V
typedef T & result; &$fe%1#
} ; 2 @g'3M
template < typename T1, typename T2 > C !81Km5
struct result_2 SGMLs'D
{ 5gWn{[[e)y
typedef T1 & result; =:(8F*Q
} ; 8Z>ZjNG
template < typename T > uY;-x~Z
typename result_1 < T > ::result operator ()( const T & r) const 7SE=otZ>
{ 7>EjP&l
return (T & )r; k*\=IacX0
} E)%]?/w
template < typename T1, typename T2 > GeN8_i[
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const o>{+vwK
{ XA{tVh
return (T1 & )r1; hQrO8T?2
} K"1xtpy
} ; >G<AyS&z*
zH8l-0I+$
template <> JZ&]"12]fR
class holder < 2 > : g/H N9
{ `zAo IQ
public : j3F[C:-zY
template < typename T > ]*-9zo0
struct result_1 -\yaP8V
{ ZWW:-3
typedef T & result; Y'kD_T`f,
} ; + oyW_!(
template < typename T1, typename T2 > D.|h0gU
struct result_2 $H ^hK0?'
{ m*h
d%1D
typedef T2 & result; NG@9}O
} ; o
Wg5-pMWZ
template < typename T > <2w@5qL
typename result_1 < T > ::result operator ()( const T & r) const BvpGP
{ ymybj
return (T & )r; e-f_#!bW
} s]0x^"#B
template < typename T1, typename T2 > c]O3pcU
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const Y;S+2])R2
{ T$13"?sr=
return (T2 & )r2; '.oEyZA;o
} "2(4?P
} ; Y+ P\5G
/kyuL]6
*iS<]y
新的holder变成了holder<int>, holder<n>的n个参数的operator()会返回第n个参数的值。而_1,_2也相应变为picker<holder<1> >, picker<holder<2> >。 G}mJtXT#=
现在让我们来看看(_1 = _2)(i. j)是怎么调用的: $Y][-8{t
首先 assignment::operator(int, int)被调用: 2#5SI
<R}(UK
return l(i, j) = r(i, j); [|V<e+>T/
先后调用holder<1>::operator()(int, int)和holder<2>::operator()(int, int) v]gJ 7x
P5Ms
X~mT
return ( int & )i; a;m-Vu!
return ( int & )j; &| el8;D
最后执行i = j; H Kx2QFB
可见,参数被正确的选择了。 R<)7,i`F
yWu80C8q
,6,#Lc
Z2`(UbG}
cEO g
八. 中期总结 Z<z(;)?c
目前的结果是这样的,为了支持一个操作符,我们需要作如下几件事: Z6SM7?d
1。 实现一个functor,该functor的operator()要能执行该操作符的语义 9f;\fe
2。 在该functor中实现result_1至result_n,其中n是支持参数的最大值。 -uenCWF\#
3。 在picker中实现一个操作符重载,返回该functor o6
Ix !O&_6s
#
9Z];<g
RvvK`}/6
f$9V_j-K+
#/!a=0
九. 简化 8.Wf^j$+{
很明显,要支持一个操作符所要做的工作太多了,而且在每个functor中申明result_1至result_n,可见如果n发生变化,维护的开销极大。 B|gyr4]
我们现在需要找到一个自动生成这种functor的方法。 Z ) qc-~S
首先,我们注意到result_x的形式很统一。对于各种操作符,其返回值无非下列几种: j*<H18^G
1. 返回值。如果本身为引用,就去掉引用。 cD
Z]r@AQ
+-*/&|^等 LHKawEZ
2. 返回引用。 BdB/`X*
=,各种复合赋值等 N|?"=4Z?
3. 返回固定类型。 |2AK~t|t
各种逻辑/比较操作符(返回bool) j%Y`2Ra
4. 原样返回。 6;9SU+/
operator, :CNWHF4$
5. 返回解引用的类型。 ]$!-%pNv
operator*(单目) {LVii}<
6. 返回地址。 ,95Nj h
operator&(单目) =K~<& l8
7. 下表访问返回类型。 BZ<Q.:)
operator[] 4]u53`
8. 如果左操作数是一个stream,返回引用,否则返回值 gI6./;;x
operator<<和operator>> p ElF,Y
D`,W1Z#
OK,这样我们将返回值类型总结为以上8种,就可以将各种result_x从functor中剥离出来了。 d%NO_=I.
例如针对第一条,我们实现一个policy类: 3i=+ [
|"?0H#
template < typename Left > [>Z~&cm
struct value_return ,*%%BTnR
{ ~~,\BhG?
template < typename T >
TB\#frG
struct result_1 Ey A}
{ uj,YCJ8UZs
typedef typename const_value < typename Left::template result_1 < T > ::result_type > ::value_type result_type; IQf:aX
} ; yNi/JM
p)RASIB
template < typename T1, typename T2 > \-$wY%7
struct result_2 AK%`EsI^
{ l_5]~N
typedef typename const_value < typename Left::template result_2 < T1, T2 > ::result_type > ::value_type result_type; *=mtt^yZ
} ; 8-3]Bm!
} ; 9^QiFgJy
5Jh=${
='a[(C&Y
其中const_value是一个将一个类型转为其非引用形式的trait e<6fe-g9;
<xOXuve
下面我们来剥离functor中的operator() ({i}EC7{
首先operator里面的代码全是下面的形式: W~2`o*\l
Vb az#I
return l(t) op r(t) 1[OCoj o<
return l(t1, t2) op r(t1, t2) w2_$>z
return op l(t) ~cQ./G4
return op l(t1, t2) [KMW*pA7
return l(t) op *,q ?mO
return l(t1, t2) op C;];4[XR
return l(t)[r(t)] d5T M_C
return l(t1, t2)[r(t1, t2)] b1JXC=*@
p;zV4uSv
很自然的,我们会想到用函数替代这种操作符行为以获得更加一致的形式: !f[LFQD
单目: return f(l(t), r(t)); FJomUVR .
return f(l(t1, t2), r(t1, t2)); rg64f'+Eug
双目: return f(l(t)); X*hY?'Rp
return f(l(t1, t2)); YAQ]2<H
下面就是f的实现,以operator/为例 Fy37I/#)r&
c1B<