一. 什么是Lambda @\|_
所谓Lambda,简单的说就是快速的小函数生成。 r<K(jG[:{f
在C++中,STL的很多算法都要求使用者提供一个函数对象。例如for_each函数,会要求用户提供一个表明“行为”的函数对象。以vector<bool>为例,如果想使用for_each对其中的各元素全部赋值为true,一般需要这么一个函数对象, GliwY_
k.uMp<)D
zaah^.MA|
MYla OT
class filler 5]n[]FW
{ V}dJ.I /#
public : FrTi+& <
void operator ()( bool & i) const {i = true ;} G]+&!4
} ; k`0>36
A%`[mc]4#
V'kX)$
这样实现不但麻烦,而且不直观。而如果使用lambda,则允许用户使用一种直观和见解的方式来处理这个问题。以boost.lambda为例,刚才的问题可以这么解决: zUKmx y@
p 3 w
ptDY3n~'
N F+iza;DP
for_each(v.begin(), v.end(), _1 = true ); y^%n'h{
?YZ- P{rTS
=at@ Vp/y
那么下面,就让我们来实现一个lambda库。 7(qE0R&@
P"W2(d
&Q>k7L!
KVD8YfF
二. 战前分析 [-\%4
首先要说明的是,我并没有读过boost.lambda或其他任何lambda库的代码,因此如代码有雷同,纯属巧合。 ^:#D0[
开始实现以前,首先要分析出大致的实现手法。先让我们来看几段使用Lambda的代码 D@Vt^_
>sK!F$
;?8_G%va
for_each(v.begin(), v.end(), _1 = 1 ); tS|(K=$
/* --------------------------------------------- */ xYmxc9)2
vector < int *> vp( 10 ); ,=Mt`aN
transform(v.begin(), v.end(), vp.begin(), & _1);
|QU <e
/* --------------------------------------------- */ oW<5|FaN
sort(vp.begin(), vp.end(), * _1 > * _2); 9\/xOwR
/* --------------------------------------------- */ f7=((5N
int b = * find_if(v.begin, v.end(), _1 >= 3 && _1 < 5 ); NMa}
<
/* --------------------------------------------- */ p(~Yx3$*
for_each(vp.begin(), vp.end(), cout << * _1 << ' \n ' ); :a$\/E =
/* --------------------------------------------- */ ~nrK>%
for_each(vp.begin(), vp.end(), cout << constant( ' \n ' ) << * _1); 0URji~?|x
TNGU6j}oq
BsEF'h'Owh
hS)'a^FV
看了之后,我们可以思考一些问题: S4G^z}{_
1._1, _2是什么? *QLI3B9V
显然_1和_2都满足C++对于标识符的要求,可见_1和_2都是对象。 b*`lk2oMa/
2._1 = 1是在做什么? ;j^H)."A\
既然_1是一个对象,那么_1的类必然重载了operator=(int)。那么operator=返回什么呢?该函数所返回的对象被传入for_each的第3个参数,可见其返回了一个函数对象。现在整个流程就很清楚了。_1 = 1调用了operator=,其返回了一个函数对象,该函数对象能够将参数1赋值为1。 "J4WzA%i
Ok,回答了这两个问题之后,我们的思路就很清晰了。如果要实现operator=,那么至少要实现2个类,一个用于产生_1的对象,另一个用于代表operator=返回的函数对象。 Ed_N[I
.7l&1C)i
*g6n
三. 动工 qWODs
首先实现一个能够范型的进行赋值的函数对象类: EJsM(iG]~M
.w0s%T,8}^
cUY`97bn
M7@2^G]p
template < typename T >
8DegN,?
class assignment a>GyO&+Dkg
{ ~S8* t~
T value; !t gi
public : >U%gctIg
assignment( const T & v) : value(v) {}
[/e<l&y
template < typename T2 > bI:zp!-.
T2 & operator ()(T2 & rhs) const { return rhs = value; } hJZV}a|
} ; JwAYG5W
f}x.jxY?
22.8PO0
其中operator()被声明为模版函数以支持不同类型之间的赋值。 Bs O+NP
然后我们就可以书写_1的类来返回assignment wM2*#
Zo g']=
;xzUE`uUfJ
T[j#M+p
class holder ZuS0DPS`L
{ #6+@M
public : Q$="_y2cTA
template < typename T > hM{{\yZS
assignment < T > operator = ( const T & t) const Uc@Ao:
{ =y0C1LD+
return assignment < T > (t); B2C$N0R#
} {\c(ls{
} ; J2'Nd'
Yy)tmq
`/EGyN6X
由于该类是一个空类,因此我们可以在其后放心大胆的写上: w+1|9Y
A^)?Wt%*
static holder _1; 0V'nK V"|
Ok,现在一个最简单的lambda就完工了。你可以写 z@B=:tf
Fsif6k=4
for_each(v.begin(), v.end(), _1 = 1 ); %F-ZN^R
而不用手动写一个函数对象。 !V
i@1E
f!!V${)X
X@K-^8
P!+'1KR
四. 问题分析 _nbBIaHN{
虽然基本上一个Lambda已经初步实现出来了,但是仔细想想,问题也是很多的。 `C$:Yf]%nG
1, 我们现在是把_1和functor看成两个不同的存在,会导致代码的重复。 bO'Sgc[]
2, 目前这个Lambda还无法实现如_1 = 2 = 3这样的链式操作。
@I_8T$N=
3, 我们没有设计好如何处理多个参数的functor。 =8; {\
下面我们可以对这几个问题进行分析。 aC%m- m
aVK3?y2
五. 问题1:一致性 D"ND+*Q[X
首先来看看1,合并_1和functor的最佳方法就是把_1本身也变成functor。那么_1的operator()会做什么事情呢?| b\-&sM(W"
很明显,_1的operator()仅仅应该返回传进来的参数本身。 f]JM /
)6|yb65ZUX
struct holder rL+!tH
{ ]3KhgK%c8
// XT@-$%u
template < typename T > Gu2P\I2zx
T & operator ()( const T & r) const &8l%T'gd
{ d5D$&5Ec
return (T & )r; n&-qaoNl
} 3b+d"`Y^S
} ; iVy7elT;R
YN!>}
这样的话assignment也必须相应改动:
FE2f'e
&Nczv"TM
template < typename Left, typename Right > 2\7`/,U6
class assignment rzh#CnL3
{ pO ml8SQf
Left l; ]y,==1To
Right r; rld67'KcE
public : `eIenA
assignment( const Left & l, const Right & r) : l(l), r(r) {} rmE" rf
template < typename T2 > @>E2?CV
T2 & operator ()(T2 & rhs) const { return l(rhs) = r; } 11<KpxKpk
} ; Bh=u|8yxc
b+f'[;
同时,holder的operator=也需要改动: ~ ; -! n;
N1|$$9G+
template < typename T > ZE2$I^DY-
assignment < holder, T > operator = ( const T & t) const 0IfKJ*]M
{ XI22+@d6
return assignment < holder, T > ( * this , t); ]K/DY Do-
} *T~Ve;3h;
ub;ZtsM,%
好,这样holder也成为了一个functor,这为我们以后添加功能节省了很多代码。 mw='dFt
你可能也注意到,常数和functor地位也不平等。 $ep.-I>
{|1Y:&M?
return l(rhs) = r;
^V#@QPK9
在这一句中,r没有调用operator()而l调用了。这样以后就要不时的区分常数和functor,是不良的设计。 lsy?Ac
那么我们仿造holder的做法实现一个常数类: GQ9\'z#+
1$%V{4bJ
template < typename Tp > ^sVX)%
class constant_t 4)U.5FBk
)
{ ?84
s4BpV1
const Tp t; @ ]/AjjLt
public : %Mk0QKzUo
constant_t( const Tp & t) : t(t) {} /ew
Ukc8,
template < typename T > #1c_ev H
const Tp & operator ()( const T & r) const H
Ge0hl[n
{ DM}YJ
return t; *{yK
8
} p\JfFfC
} ; %5A+V0D0'
mL_j4=ER@
该functor的operator()无视参数,直接返回内部所存储的常数。
AiK
下面就可以修改holder的operator=了 jSwf*u
\o/n
template < typename T > /6h(6 *JI
assignment < holder, constant_t < T > > operator = ( const T & t) const ^9ePfF)5
{ ;uW}`Q<
return assignment < holder, constant_t < T > > ( * this , constant_t < T > (t)); tPGJ<30
}
8`fjF/
$`-4Ax4%
同时也要修改assignment的operator() Wh%ucX&
T+<A`k: -
template < typename T2 > yRiP{$E
T2 & operator ()(T2 & rhs) const { return l(rhs) = r(rhs); } &'DU0c&
现在代码看起来就很一致了。 ngat0'oa
|'{zri|A"
六. 问题2:链式操作 aMvI?y {
现在让我们来看看如何处理链式操作。 7
<Q5;J&;
其实问题1已经为我们处理掉了大量的问题。如果_1,functor,常量彼此之间不统一为functor,那么链式操作的时候就要时刻小心一个对象是_1还是functor还是常量,会大大增加编码的难度。 )I$q 5%q8
事实上,首先要解决的是,如何知道一个functor的operator()的返回值的类型。遗憾的是,我并没有找到非常自动的办法,因此我们得让functor自己来告诉我们返回值的类型。 ;\\@q"n%<
比较麻烦的是,operator()的返回值一般和其参数的类型相关,而operator()通常是一个模版函数,因此其返回值类型并不能用一个简单的typedef来指定,而必须实现一个trait。 Vgyew9>E
现在我们在assignment内部声明一个nested-struct 6p?JAT5
,I_^IitN
template < typename T > &bp=`=*
struct result_1 Ie4 hhW
{ HjGyj/78w
typedef typename ref < typename Left::result_1 < T > ::result > ::reference result; ]f_6 '|5A
} ; 9>g,
'I /aboDB
那么如果参数为T,其返回值类型就为result_1<T>::result。上面代码的ref<T>为一个类型转换类,作用是返回T的引用。不直接加上&符号的原因是如果T本身就是Q的引用Q&,那么Q&&是非法的。因此ref的实现即为:
stk9Ah
>+
4huRb
template < typename T > 9 `w)
struct ref HH@qz2 w
{ |)K]U
typedef T & reference; h?FmBK'BAd
} ; S -'fS2
template < typename T > qq1 - DG
struct ref < T &> mBG=jI "xh
{ [_.5RPJP8
typedef T & reference; mUz\ra;z
} ; K
a(J52
#~.w&~:
有了result_1之后,就可以把operator()改写一下: !Wy[).ZAf
zdEPDdB
template < typename T > }LijnHH.
typename result_1 < T > ::result operator ()( const T & t) const " $ew~;z
{ Iz{R}#8CZ
return l(t) = r(t); BZXUwqEh
} =T7A]U]
可能大家已经注意到我定义assignment的operator()的返回类型的时候,是直接将其定义为Left的operator()返回类型的引用形式,如果实际上处理的对象的operator=并不是按照常理来声明的,那么这段代码可能就编译不过。这的确是一个很麻烦的事情。实际上,在gcc下,使用typeof关键字可以很容易的得到该类型的operator=的返回类型,就可以让这段代码变得更有通用性。然而为了实现可移植性,我不得不放弃这个诱人的想法。 yT#{UA^
同理我们可以给constant_t和holder加上这个result_1。 9gEssTkts
Myq5b`z
有了这个result_1,链式操作就简单多了。现在唯一要做的事情就是让所有的functor都重载各种操作符以产生新的functor。假设我们有add和divide两个类,那么 o, !T2&}
_1 / 3 + 5会出现的构造方式是: S9>0t0
_1 / 3调用holder的operator/ 返回一个divide的对象 acw4B5]
+5 调用divide的对象返回一个add对象。 }QsZ:J.
最后的布局是: 2d {y M(=(
Add sqS=qC
/ \ fz3lV
Divide 5 ~35U]s@v
/ \ yin'vgQ
_1 3 ?l $Nf@-
似乎一切都解决了?不。 n9\]S7]52
你可以想象一下一个完整的Lambda库,它必然能够重载C++几乎所有的操作符。假设其重载了10个操作符,那么至少会有10个代表这些操作符的functor类。大体上来讲,每一种操作符所对应的functor都应当能够由链式操作产生别的任意一种操作符所对应的functor。(例如:*_1 = 2既是由operator*的functor产生operator=的functor)。可想而知这样一共能产生10*10=100种产生方式。这是对编码的一个大挑战。 ]wWPXx[>/
如何简化这个问题呢?我们不妨假定,任意一种操作符的functor,都能够产生任意一种操作符的functor,这样,每一种操作符的functor都拥有一样的产生方案。如果某种转换确实是不合法的(例如:A/B=C无论如何也不可能合法),那么在试图产生新functor的时候会出现编译错误。幸好C++的模版是如果不使用就不编译的,因此这种编译错误不会干扰到正常的使用,这正是我们所要的。 WwUv5GZTW
OK,我们的方法呼之欲出了。既然所有的functor都具有一样的产生方案,那么不如大家都不要实现,等到最后统一的在所有的functor里面加上这么一系列的产生代码吧。例如,如果要添加从某functor XXX到operator=的functor的产生代码: C{q :_M;
ZZ.m(ATR
template < typename Right > D^-7JbE]
assignment < XXX, typename picker_maker < Right > ::result > operator = ( const Kmdlf,[3d
Right & rt) const yx<WSgWZ[
{ Qo1eXMW
return assignment < XXX, typename picker_maker < Right > ::result > ( * this , rt); vYU;_R
} hAjM1UQ,Y
下面对该代码的一些细节方面作一些解释 d)"?mD:m/M
XXX指的是原来的functor的类型,picker_maker<T>是一个类型变换的trait,如果T是一个常量,那么他会返回constant_t<T>,否则返回T本身。 ;9}pOzF1q
因此如果该函数声明在assignment的内部,那么就实现了连等,如果声明在的dereference(解引用)的内部,就允许(*A = B)的行为发生。 5zIAhg@o:q
最后,如何把这些函数塞到各个functor的声明里边呢?当然可以用宏,但是。。。大家都知道这样不好。 _ %x4ty
除了宏之外还可以用的方式就是继承。我们可以写一个类叫做picker,该类实现了所有的如上的产生函数。然后让所有的functor继承自它。 i]#+1Hf
且慢,也许立刻就有人跳出来说:这样的话那个XXX怎么写呢?这样不是会导致循环依赖么?这样不是会有downcast么? X2xuwA
正解,让picker做基类确实不是一个好主意。反过来,让picker继承functor却是一个不错的方法。下面是picker的声明: vc]cNz:mQ
Y&^ P"Dw
template < class Action > 1 `7<2w
class picker : public Action E3*\
^Q_
{ {"
4e+y
public : ad_`x
picker( const Action & act) : Action(act) {} \6 93kQ
// all the operator overloaded ee/&/Gt
} ; W},b{NT
3w!c`;c%
Picker<T>继承自T,唯一的作用就是给T添加上了各种操作符的重载函数。 /2RajsK
现在所有参与行动的functor都要套上一层picker, _1被声明为 picker<holder>, 并且holder中所重载的操作符除了operator()之外全部被移到了picker内。而picker中的操作符重载的返回的functor也必须套上一个picker: )Y8",Ig
PD LpNTBf
template < typename Right > {h KjD"?
picker < assignment < Action, typename picker_maker < Right > ::result > > operator = ( const Right & rt) const ?9X&tK)E-
{ ne>g?"Pex{
return assignment < Action, typename picker_maker < Right > ::result > ( * this , rt); fbkd "7u
} N7s'6(`=X
x+@&(NMP5
Piker_maker返回的也是picker<T>,或者picker<constant_t<T> > \Fe_rh
使用picker还带来一个额外的好处。之前提到picker_maker要区分functor和常量,有了picker,区分的方法就非常简单了:凡是属于picker<T>的都是functor,否则就是常量。 :Yj)CGl$
\i[BP
template < typename T > struct picker_maker Z^9/v
{ )C. yF)Ql
typedef picker < constant_t < T > > result; :v L1}H<
} ; 1H,g=Y4f%
template < typename T > struct picker_maker < picker < T > > 7 ua6l[c
{ `:eViVl6e
typedef picker < T > result; ,JEbd1Uf
} ; >z`,ch6~
A, PlvI
下面总的结构就有了: 1[*{(e
functor专心模拟操作符的行为,并实现一个result_1来告诉别人自己的返回类型。 tyDY'W\]
picker专心负责操作符之间的产生关系,由它来联系操作符合functor。 lI/0:|l
picker<functor>构成了实际参与操作的对象。 7DfTfTU6
至此链式操作完美实现。 "W#t;;9Wz
aRc '
) ){xlFA}
七. 问题3 sIl33kmv
如何使用多参数的函数对象呢?考虑_1=_2,这个functor必须接受2个参数,因此所产生的assignment对象的operator()必须能接收2个参数。 |Cdvfk
Kwhdu<6
template < typename T1, typename T2 > XIWm>IQ[)
??? operator ()( const T1 & t1, const T2 & t2) const o."rxd
{ Sc]P<F7N]
return lt(t1, t2) = rt(t1, t2); 2Nj9U#A
} 8:.nEo'
e2C<PGUUB
很明显,这个函数的返回类型会依赖于T1,T2,因此result_1已经无法适用,我们就只好再写一个result_2: Ft@Wyo`^
!%Y~~'5 h
template < typename T1, typename T2 > ZE`lr+_Y
struct result_2 ==cd>03()
{ 60Z]M+8y8
typedef typename ref < typename Left::result_2 < T1, T2 > ::result > ::reference result; ?Mp1~{8
} ; <g9"Cr`
to6;?uC+|i
显然,各个functor似乎根本不理会各个参数那个是_1, 那个是_2, 那么最后是怎么选择的呢? z\/53Sy<
这个差事就留给了holder自己。 6TH!vuQ1(
d3]hyTqbtm
4q$H
template < int Order > -K[782Q
class holder; p[2GkP
template <> $A}QY5`+~S
class holder < 1 > !eJCM`cp
{ ,5|d3dJS
public : <TNk?df7
template < typename T > ^\:2}4Uj_
struct result_1 jvzBh-!
{ Z7jX9e"L
typedef T & result; o;[bJ
Z\^x
} ; uvA(Rn
template < typename T1, typename T2 > PzY)"]g
struct result_2 T!Sj<,r+j
{ eu'1H@vX(
typedef T1 & result; .~}z4r
} ; j|e[s ?d
template < typename T > QT#6'>&7-b
typename result_1 < T > ::result operator ()( const T & r) const nB5Am^bP
{ wE).>
return (T & )r; x"(9II*
} T ^JuZG
template < typename T1, typename T2 > FXo2Y]K3`L
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const +dkS/b
{ ?G?gy2
return (T1 & )r1; l
oqvi
} Gowp
<9 F
} ; a-n4:QT
iS@\ =CK
template <> |)W!jC&k
class holder < 2 > Ak~4|w-
{ ;TZGC).6
public : tL0`Rvl
template < typename T > S2~@nhO`U(
struct result_1 =z:U~D
{ g$j6n{Yl
typedef T & result; qvt-
} ; /f1'm@8;
template < typename T1, typename T2 > *rqm8z50a
struct result_2 R#4^s
{ FoPginZ]J
typedef T2 & result; J?P]EQU
} ; j.3o W
template < typename T > ,2 WH/"
typename result_1 < T > ::result operator ()( const T & r) const m%QqmTH
{ r9ke,7?
return (T & )r; iilyw_$H
} |Vx~fK S\
template < typename T1, typename T2 > -O&"|
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const `HUf v@5
{ !v!N>f4S$
return (T2 & )r2; iUr xJh
} dDKqq(9(`
} ; L)-*,$#<oW
n_$yV:MuT!
6CNS%\A
新的holder变成了holder<int>, holder<n>的n个参数的operator()会返回第n个参数的值。而_1,_2也相应变为picker<holder<1> >, picker<holder<2> >。 ^{[`=P'/
现在让我们来看看(_1 = _2)(i. j)是怎么调用的:
U
5`y
首先 assignment::operator(int, int)被调用: @~jxG%y86
zj]b&In6;
return l(i, j) = r(i, j); )LswSV
先后调用holder<1>::operator()(int, int)和holder<2>::operator()(int, int) 7unA"9=[4V
I{dl% z73
return ( int & )i; i=QqB0
return ( int & )j; +Z?[M1g
最后执行i = j; 6b:DJ
可见,参数被正确的选择了。 ~HP
LV
eX<K5K.B
wsg//Ec]
N4 [E~-
:$"7-a%f
八. 中期总结 R'EW7}&
目前的结果是这样的,为了支持一个操作符,我们需要作如下几件事: U($^E}I2(
1。 实现一个functor,该functor的operator()要能执行该操作符的语义 L? ;/cO^
2。 在该functor中实现result_1至result_n,其中n是支持参数的最大值。 ,0T)Oc|HL/
3。 在picker中实现一个操作符重载,返回该functor -
8syjKTg
<q7s`,rG
\7E`QY4
NyJnOw(
4/L>&%8V
umDtp\
九. 简化 IYNMU\s
很明显,要支持一个操作符所要做的工作太多了,而且在每个functor中申明result_1至result_n,可见如果n发生变化,维护的开销极大。 MOV =n75
我们现在需要找到一个自动生成这种functor的方法。 >.Q0Tx!P
首先,我们注意到result_x的形式很统一。对于各种操作符,其返回值无非下列几种: ?~qC,N [
1. 返回值。如果本身为引用,就去掉引用。 rh $1-Y
+-*/&|^等 6=>7M
b$
2. 返回引用。 k.Zll,s
=,各种复合赋值等 96W4c]NT
3. 返回固定类型。 md6*c./Z
各种逻辑/比较操作符(返回bool) 3%NE/lw1
4. 原样返回。 K<,Y^3]6?
operator, N&B>#:
5. 返回解引用的类型。 dy_.(r5[L]
operator*(单目) DyI2Ye
6. 返回地址。 $DV-Ieb
operator&(单目) fH!=Zb_{8
7. 下表访问返回类型。 a R#Cot
operator[] '?R =P
8. 如果左操作数是一个stream,返回引用,否则返回值 nx :)k-p_[
operator<<和operator>> I2*oTUSik
^"`Z1)V
OK,这样我们将返回值类型总结为以上8种,就可以将各种result_x从functor中剥离出来了。 (^S5Sc=
例如针对第一条,我们实现一个policy类: `9EVB;
2nx8iA
template < typename Left > tG 7+7Z=
struct value_return $Z7:#cZ Y
{ |B1Af
template < typename T > !?r/ 4
struct result_1 [i9[Mj
{ /$OIlu
typedef typename const_value < typename Left::template result_1 < T > ::result_type > ::value_type result_type; ^4hc+sh0D
} ; ,'-?:`hP'
,%= '>A
template < typename T1, typename T2 > aa=b<Cd
struct result_2 !@yQK<0
{ 4H7Oh*P\j
typedef typename const_value < typename Left::template result_2 < T1, T2 > ::result_type > ::value_type result_type; IuWX*b`v
} ; LO>8 j:
} ; !>|`ly$6
cX"G7Bh
3qcpf:
其中const_value是一个将一个类型转为其非引用形式的trait q+J0}y{#8)
_U=S]2QW
下面我们来剥离functor中的operator() 'X ~Ab
首先operator里面的代码全是下面的形式: 2e\Kw+(>{
MVuP
|&:n
return l(t) op r(t) 7X:hIl
return l(t1, t2) op r(t1, t2) ypT9 8
return op l(t) &O{t^D)F
return op l(t1, t2) d:3= 1x
return l(t) op <