一. 什么是Lambda hgMnO J
所谓Lambda,简单的说就是快速的小函数生成。 ob()+p.k K
在C++中,STL的很多算法都要求使用者提供一个函数对象。例如for_each函数,会要求用户提供一个表明“行为”的函数对象。以vector<bool>为例,如果想使用for_each对其中的各元素全部赋值为true,一般需要这么一个函数对象, X/C54%T ~
1pBsr(
3 %{'Uh,
%nK15(
class filler S7~l%G>]b
{ nD{;4$xP`
public : ) a2m<"
void operator ()( bool & i) const {i = true ;} GA*Khqdid
} ; & ;x1Rx
&|,qsDK(
OEq e^``!
这样实现不但麻烦,而且不直观。而如果使用lambda,则允许用户使用一种直观和见解的方式来处理这个问题。以boost.lambda为例,刚才的问题可以这么解决: T;vPR,]rz
QSQ\@h;E
k>@^M]%
MyS7AL
for_each(v.begin(), v.end(), _1 = true ); 'c\TMb.
b|C,b"$N0
XdXS^QA.s
那么下面,就让我们来实现一个lambda库。 ^i,0n}>
F[qIfh4
YuZ
C{Xk/Er5<
二. 战前分析 *d*;M>
首先要说明的是,我并没有读过boost.lambda或其他任何lambda库的代码,因此如代码有雷同,纯属巧合。 |"(3]f\
开始实现以前,首先要分析出大致的实现手法。先让我们来看几段使用Lambda的代码 zAdVJ58H
yeh adm\
IF21T
for_each(v.begin(), v.end(), _1 = 1 ); G6g=F+X2
/* --------------------------------------------- */ "I1M$^8n
vector < int *> vp( 10 ); d}G."wnG9,
transform(v.begin(), v.end(), vp.begin(), & _1); 6je%LHhL
/* --------------------------------------------- */ BN>$LL
sort(vp.begin(), vp.end(), * _1 > * _2); AG!a=ufc0
/* --------------------------------------------- */ \7?MUa.4
int b = * find_if(v.begin, v.end(), _1 >= 3 && _1 < 5 ); AZ@Zo'
/* --------------------------------------------- */ Bwvc@(3v
for_each(vp.begin(), vp.end(), cout << * _1 << ' \n ' ); [Z&s0f1Qb
/* --------------------------------------------- */ | gxB;
GG
for_each(vp.begin(), vp.end(), cout << constant( ' \n ' ) << * _1); kj"_Y"q=
WX$^[^=HC
544I#!
u+T, n
看了之后,我们可以思考一些问题:
SCC/
<o
1._1, _2是什么? $ }bC$?^
显然_1和_2都满足C++对于标识符的要求,可见_1和_2都是对象。 _|#|mb4Fe
2._1 = 1是在做什么? \.-y
LS.
既然_1是一个对象,那么_1的类必然重载了operator=(int)。那么operator=返回什么呢?该函数所返回的对象被传入for_each的第3个参数,可见其返回了一个函数对象。现在整个流程就很清楚了。_1 = 1调用了operator=,其返回了一个函数对象,该函数对象能够将参数1赋值为1。 FbT&w4Um=
Ok,回答了这两个问题之后,我们的思路就很清晰了。如果要实现operator=,那么至少要实现2个类,一个用于产生_1的对象,另一个用于代表operator=返回的函数对象。 ].+G-<.:
F nRxc
_ r)hr7
三. 动工 ,,-3p#Pbw
首先实现一个能够范型的进行赋值的函数对象类: p{QKj3ov
u>Kvub
?ew]i'9(
N=Yi:+
template < typename T > }U1{&4Ph
class assignment WmBnc#>gK
{
x a,LV
T value; ]=$ay0HC
public : S6:gow(wU
assignment( const T & v) : value(v) {} xqZ%c/I3q
template < typename T2 > |?b"my$g$
T2 & operator ()(T2 & rhs) const { return rhs = value; } s+t eYL#Zi
} ; F4l6PGxF&\
QU;C*}0Zl
K&oO+ G^f
其中operator()被声明为模版函数以支持不同类型之间的赋值。 K%@SS8!oy
然后我们就可以书写_1的类来返回assignment f3&//h8
+f~3FXM
aQuy*\$$
Ss/="jC
class holder mq}
#{
{ <p8y'KAlc
public : K\r=MkA.>
template < typename T > #FM 'S|
assignment < T > operator = ( const T & t) const E8 )*HOT_T
{ 30-wTcG
return assignment < T > (t); fxa^SV
} /1GZN *I
} ; FA GVpO[
U9OF0=g
aM1JG$+7 G
由于该类是一个空类,因此我们可以在其后放心大胆的写上: cHd39H9
d$
7b
static holder _1; :G)x+0u
Ok,现在一个最简单的lambda就完工了。你可以写 %'<
qhGJ
P Qay
sdb
for_each(v.begin(), v.end(), _1 = 1 ); +u.L6GcB
而不用手动写一个函数对象。 f%l#g ]]
:
s3Vl
9e6{(
mw%_yDZ{
四. 问题分析 Z@umbyM
虽然基本上一个Lambda已经初步实现出来了,但是仔细想想,问题也是很多的。 gQGiph |
1, 我们现在是把_1和functor看成两个不同的存在,会导致代码的重复。 eT?LMBn\
2, 目前这个Lambda还无法实现如_1 = 2 = 3这样的链式操作。 +t6m>IBu
3, 我们没有设计好如何处理多个参数的functor。 Y'R1\Go-
下面我们可以对这几个问题进行分析。 [K QZHIe
T!E LH!
五. 问题1:一致性 40ZB;j$l
首先来看看1,合并_1和functor的最佳方法就是把_1本身也变成functor。那么_1的operator()会做什么事情呢?| zYJxoC{
很明显,_1的operator()仅仅应该返回传进来的参数本身。 '^AXUb
(J#3+I
struct holder 4 ETVyK|
{ nwVtfsb
// ] lTfi0}g_
template < typename T > YiMecu
T & operator ()( const T & r) const s os&
{ 34+}u,=
return (T & )r; Fb-TCq1y#
} >iV(8EgBS
} ; IA!Kpg
W
EeJ]>
1
这样的话assignment也必须相应改动: lvffQ_t
=Q/i<u
template < typename Left, typename Right > =jh:0Q<43+
class assignment upKrr
{ #nz$RJsX
Left l; $Q4b~
Right r; RT9@&5>il
public : g?sFmD
assignment( const Left & l, const Right & r) : l(l), r(r) {} 2
{0VyLx
template < typename T2 > ,|/$|$'
T2 & operator ()(T2 & rhs) const { return l(rhs) = r; } omu&:)
g
} ; o~ed0>D-LS
-OxHQ
同时,holder的operator=也需要改动: a#=-Aj-
=7>~u
template < typename T > QJ?!_2Ax
assignment < holder, T > operator = ( const T & t) const st>t~a|T
{ =uTV\)
return assignment < holder, T > ( * this , t); 4dAhJjhgD
} }+1o D{
x.Y,]wis
好,这样holder也成为了一个functor,这为我们以后添加功能节省了很多代码。 Qa+gtGtJ
你可能也注意到,常数和functor地位也不平等。 ~Otf
" <
T~E83Jw
return l(rhs) = r; `}l%Am
在这一句中,r没有调用operator()而l调用了。这样以后就要不时的区分常数和functor,是不良的设计。 ualtIHXK)
那么我们仿造holder的做法实现一个常数类: +V^_ksi\
6iC:l%|u
template < typename Tp > h'+ swPh
class constant_t }rZp(FG@*
{ 8!fwXm
const Tp t; ,5,4 Qf7
public : Tc:`TE=2
constant_t( const Tp & t) : t(t) {} &2J|v#$F
template < typename T > :W"ITY(
const Tp & operator ()( const T & r) const 2)YLs5>W%
{ 5**xU+&
return t; >%,tyJ~
} mLO6`]p{H
} ; tK*f8X+q
^=j$~*(LmX
该functor的operator()无视参数,直接返回内部所存储的常数。 <MY_{o8d
下面就可以修改holder的operator=了 #[IQmU23
U:ZklDW
template < typename T > Y]P]^3
assignment < holder, constant_t < T > > operator = ( const T & t) const r3#H]c
{ *K!V$8k=99
return assignment < holder, constant_t < T > > ( * this , constant_t < T > (t)); )PZ'{S
} uRJLSt9m
#qHo+M$"
同时也要修改assignment的operator() --y.q~d
\ B<(9
template < typename T2 > nsYS0
T2 & operator ()(T2 & rhs) const { return l(rhs) = r(rhs); } 2Ui)'0
现在代码看起来就很一致了。 [&6l=a
QO0T<V
六. 问题2:链式操作 'H:lR1(,
现在让我们来看看如何处理链式操作。 iz>a0~(K
其实问题1已经为我们处理掉了大量的问题。如果_1,functor,常量彼此之间不统一为functor,那么链式操作的时候就要时刻小心一个对象是_1还是functor还是常量,会大大增加编码的难度。 C)Mh
事实上,首先要解决的是,如何知道一个functor的operator()的返回值的类型。遗憾的是,我并没有找到非常自动的办法,因此我们得让functor自己来告诉我们返回值的类型。 jRzR`>5
比较麻烦的是,operator()的返回值一般和其参数的类型相关,而operator()通常是一个模版函数,因此其返回值类型并不能用一个简单的typedef来指定,而必须实现一个trait。 A:>G: X5t
现在我们在assignment内部声明一个nested-struct co#%~KqMu
DqbN=[!X~n
template < typename T > u[y>DPPx
struct result_1 ACc.&,!IZ
{ cvi+AZ=
typedef typename ref < typename Left::result_1 < T > ::result > ::reference result; B$aboL2
} ; 5{VrzzOK}
g;Bq#/w
那么如果参数为T,其返回值类型就为result_1<T>::result。上面代码的ref<T>为一个类型转换类,作用是返回T的引用。不直接加上&符号的原因是如果T本身就是Q的引用Q&,那么Q&&是非法的。因此ref的实现即为: .F&\xa{
b>?X8)f2e
template < typename T > !ZHPR:k|
struct ref >PfYHO
{ (yn!~El3
typedef T & reference; ybcQ,e
} ; ~%m-}Sxc
template < typename T > -7>vh|3
struct ref < T &> &$|k<{j[<f
{ s9zdg"c'
typedef T & reference; UPA))Iv>
} ; >DpnIWn
VWaI!bK
有了result_1之后,就可以把operator()改写一下: {C[<7ruF
o0_RU<bWN
template < typename T > =&< s*-l[
typename result_1 < T > ::result operator ()( const T & t) const 6(VCQ{
{ GjfY
return l(t) = r(t); l0 rZril
} qJKD|=_
可能大家已经注意到我定义assignment的operator()的返回类型的时候,是直接将其定义为Left的operator()返回类型的引用形式,如果实际上处理的对象的operator=并不是按照常理来声明的,那么这段代码可能就编译不过。这的确是一个很麻烦的事情。实际上,在gcc下,使用typeof关键字可以很容易的得到该类型的operator=的返回类型,就可以让这段代码变得更有通用性。然而为了实现可移植性,我不得不放弃这个诱人的想法。 4L(axjMYU
同理我们可以给constant_t和holder加上这个result_1。 Ay22-/C|@
L00,{g6wqb
有了这个result_1,链式操作就简单多了。现在唯一要做的事情就是让所有的functor都重载各种操作符以产生新的functor。假设我们有add和divide两个类,那么 z{7&= $
_1 / 3 + 5会出现的构造方式是: zsc8Lw
_1 / 3调用holder的operator/ 返回一个divide的对象 <{JHFU`^
+5 调用divide的对象返回一个add对象。 5a&[NN
最后的布局是: W:ih#YW_F
Add Jr==AfxyT
/ \ Da0E)
Divide 5 D9 OS,U/l
/ \ oOnk,U
_1 3 cL7je
似乎一切都解决了?不。 AM#VRRTU
你可以想象一下一个完整的Lambda库,它必然能够重载C++几乎所有的操作符。假设其重载了10个操作符,那么至少会有10个代表这些操作符的functor类。大体上来讲,每一种操作符所对应的functor都应当能够由链式操作产生别的任意一种操作符所对应的functor。(例如:*_1 = 2既是由operator*的functor产生operator=的functor)。可想而知这样一共能产生10*10=100种产生方式。这是对编码的一个大挑战。 =(3Qbb1i
如何简化这个问题呢?我们不妨假定,任意一种操作符的functor,都能够产生任意一种操作符的functor,这样,每一种操作符的functor都拥有一样的产生方案。如果某种转换确实是不合法的(例如:A/B=C无论如何也不可能合法),那么在试图产生新functor的时候会出现编译错误。幸好C++的模版是如果不使用就不编译的,因此这种编译错误不会干扰到正常的使用,这正是我们所要的。 Y, )'0O
OK,我们的方法呼之欲出了。既然所有的functor都具有一样的产生方案,那么不如大家都不要实现,等到最后统一的在所有的functor里面加上这么一系列的产生代码吧。例如,如果要添加从某functor XXX到operator=的functor的产生代码: |.0/~Xy-
!FX;QD@"
template < typename Right > =;9Wh!{
assignment < XXX, typename picker_maker < Right > ::result > operator = ( const g>CF|Wj
Right & rt) const
e%afK@c
{ ]3BTL7r
return assignment < XXX, typename picker_maker < Right > ::result > ( * this , rt); m>yk4@a
} DyD#4J)E
下面对该代码的一些细节方面作一些解释 BOQ2;@:3
XXX指的是原来的functor的类型,picker_maker<T>是一个类型变换的trait,如果T是一个常量,那么他会返回constant_t<T>,否则返回T本身。 s=!
y%
因此如果该函数声明在assignment的内部,那么就实现了连等,如果声明在的dereference(解引用)的内部,就允许(*A = B)的行为发生。 ;$wS<zp6
最后,如何把这些函数塞到各个functor的声明里边呢?当然可以用宏,但是。。。大家都知道这样不好。 N4}j,{#
除了宏之外还可以用的方式就是继承。我们可以写一个类叫做picker,该类实现了所有的如上的产生函数。然后让所有的functor继承自它。 k3da*vwE
且慢,也许立刻就有人跳出来说:这样的话那个XXX怎么写呢?这样不是会导致循环依赖么?这样不是会有downcast么? @kenv3[Lc
正解,让picker做基类确实不是一个好主意。反过来,让picker继承functor却是一个不错的方法。下面是picker的声明: be |k"s|6)
y{0`+/\`
template < class Action > h
\hQ
class picker : public Action hWfJh0I
{ :Ag]^ot
public : eu@-v"=w
picker( const Action & act) : Action(act) {} !h4S`2oZ/
// all the operator overloaded Z,M?!vK
} ; :bkACuaEn
dE7 kd=.o
Picker<T>继承自T,唯一的作用就是给T添加上了各种操作符的重载函数。 Og4 X3QG
现在所有参与行动的functor都要套上一层picker, _1被声明为 picker<holder>, 并且holder中所重载的操作符除了operator()之外全部被移到了picker内。而picker中的操作符重载的返回的functor也必须套上一个picker: <N}UwB&
9x0B9&
template < typename Right >
Z3<>Z\6D
picker < assignment < Action, typename picker_maker < Right > ::result > > operator = ( const Right & rt) const &^Zo}F2V
{ 8p,q9Ey
return assignment < Action, typename picker_maker < Right > ::result > ( * this , rt); zW,m3~XX:
} LYhgBG,
OB)Vk
Piker_maker返回的也是picker<T>,或者picker<constant_t<T> > 9$c0<~B\
使用picker还带来一个额外的好处。之前提到picker_maker要区分functor和常量,有了picker,区分的方法就非常简单了:凡是属于picker<T>的都是functor,否则就是常量。 ^0_ *AwIcN
'S@%
template < typename T > struct picker_maker kj~)#KDN
{ {+r
pMUs#
typedef picker < constant_t < T > > result; {mY<R`Ee
} ; 6a[D]46y,2
template < typename T > struct picker_maker < picker < T > > VT96ph
{ =G]} L<
typedef picker < T > result; UK*+EEv
} ; =unMgX]$
<6Q]FH!6
下面总的结构就有了: gQ3Co ./
functor专心模拟操作符的行为,并实现一个result_1来告诉别人自己的返回类型。 VI,z7
\
picker专心负责操作符之间的产生关系,由它来联系操作符合functor。 O+%Y1=S[WQ
picker<functor>构成了实际参与操作的对象。 7}(wEC
至此链式操作完美实现。 k!H;(B"s-
X+)68
%Rj:r!XB:
七. 问题3 $,otW2:)
如何使用多参数的函数对象呢?考虑_1=_2,这个functor必须接受2个参数,因此所产生的assignment对象的operator()必须能接收2个参数。 !f-mC,d
O6LZ<}oUR
template < typename T1, typename T2 > 6e&Y%O'8
??? operator ()( const T1 & t1, const T2 & t2) const O Ul+es
{ Vh$~]>t:f
return lt(t1, t2) = rt(t1, t2); LH@)((bi4v
} V|zzj[c
z)xGZ*{=
很明显,这个函数的返回类型会依赖于T1,T2,因此result_1已经无法适用,我们就只好再写一个result_2: $%ZEP>]
4SCb9|/Q
template < typename T1, typename T2 > YNA %/
struct result_2 wvvMesX<L
{ xfCq;?MupW
typedef typename ref < typename Left::result_2 < T1, T2 > ::result > ::reference result; #qWa[kB
} ; h.>6>5$n
85Zy0l
显然,各个functor似乎根本不理会各个参数那个是_1, 那个是_2, 那么最后是怎么选择的呢? p/>}{Q )Y
这个差事就留给了holder自己。 <use+C2
Y,I0o{,g
G`!#k!&r
template < int Order > dDbPM9]5
class holder;
YqX/7b+
template <> XFeHkU`C
class holder < 1 > KGdL1~
{ <^6|ZgR
public : 0M(\xO
template < typename T > u:tLO3VfJ
struct result_1 %myg67u
{ cJj4qXF
typedef T & result; ,2q LiE>
} ; +:pjQ1LsJ
template < typename T1, typename T2 > ;V`e%9.
struct result_2 "& 'h\
{ ,!_$A}@0
^
typedef T1 & result; g0GCg
} ; hE0
p>R8
template < typename T > 0FD#9r
typename result_1 < T > ::result operator ()( const T & r) const \M(0@#-$C
{ U">w3o|
return (T & )r; NBZFIFO<
} ?ORG<11a
template < typename T1, typename T2 > Fl<|/DCg
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const ~c~N _b
{ yM>:,T S
return (T1 & )r1; 37Ux2t
} I!3qb-.Q
} ; V`qHNM/t
IoWh&(+KdH
template <> @T[}]e
class holder < 2 > $K>'aI;|
{ l\<*9m<
public : *-"DZ
template < typename T > rKg~H=4x2
struct result_1 p+#$S4V
{ >5-z"f
typedef T & result; },G6IuH%
} ; Hh`x>{,|S
template < typename T1, typename T2 > ,?g}->ZB
struct result_2 \nT, NV11
{ dnt: U!TW@
typedef T2 & result; #AHX{<
} ; uz-O%R-
template < typename T > <yzgZXxIaS
typename result_1 < T > ::result operator ()( const T & r) const A3D"b9<D
{ T pXbJ]o9
return (T & )r; +|(-7"
} @9S3u#vP
template < typename T1, typename T2 > gI<e=|J6w
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const ?IgM=@
{ zzOc
# /
return (T2 & )r2; Zn{,j0;
} 2\Bt~;EIx
} ; w5zrEk#
%%`Q5I
NvCq5B$C
新的holder变成了holder<int>, holder<n>的n个参数的operator()会返回第n个参数的值。而_1,_2也相应变为picker<holder<1> >, picker<holder<2> >。 Hw[(v[v
现在让我们来看看(_1 = _2)(i. j)是怎么调用的: A/ppr.
首先 assignment::operator(int, int)被调用: 5WJof`M
V*]cF=W[A
return l(i, j) = r(i, j); qQ\&]
先后调用holder<1>::operator()(int, int)和holder<2>::operator()(int, int) z+IHt(
vX|i5P0)8
return ( int & )i; nO-1^HUl
return ( int & )j; EG=~0j ~
最后执行i = j; 8K(3{\J[V
可见,参数被正确的选择了。 S
?v^/F
z*,P^K 0T
#r{`Iv?nn
>IZ|:lsxE
-jN:~.
八. 中期总结
DVNx\t
目前的结果是这样的,为了支持一个操作符,我们需要作如下几件事: j;%RV)e
1。 实现一个functor,该functor的operator()要能执行该操作符的语义 ~+ _|J"\
2。 在该functor中实现result_1至result_n,其中n是支持参数的最大值。 gZkjh{rQ
3。 在picker中实现一个操作符重载,返回该functor ZWJ%t'kF
N0UL1[ur
B?o ?LI
"^?|=sQ
oEfy{54
P|M#S9^]
九. 简化 u~
%xU~v
很明显,要支持一个操作符所要做的工作太多了,而且在每个functor中申明result_1至result_n,可见如果n发生变化,维护的开销极大。 s
Yp?V\Y"
我们现在需要找到一个自动生成这种functor的方法。 2oL~N*^C
首先,我们注意到result_x的形式很统一。对于各种操作符,其返回值无非下列几种: #s"|8#
1. 返回值。如果本身为引用,就去掉引用。 "Yh[-[,
+-*/&|^等 UC@&! kM
2. 返回引用。 UD.&p'^ /{
=,各种复合赋值等 f9`F~6$
3. 返回固定类型。 =lyP &u
各种逻辑/比较操作符(返回bool) ds>V|}f[
4. 原样返回。 $inpiO|s
operator, o*\cV6
5. 返回解引用的类型。 YGAB2`!U
operator*(单目) Ojea~Y]Sr
6. 返回地址。 s]=bg+v?j
operator&(单目) vsFRWpq
7. 下表访问返回类型。 +Ndo$|XCy]
operator[] W.nQYH
8. 如果左操作数是一个stream,返回引用,否则返回值 .nGYx
operator<<和operator>> 4VPJv>^
X?3?R\/
OK,这样我们将返回值类型总结为以上8种,就可以将各种result_x从functor中剥离出来了。 WnATgY t
例如针对第一条,我们实现一个policy类: gOk<pRcTb=
/3]b!lFZZ
template < typename Left > K/^70;/!.
struct value_return ^F}HWpF_
{ lQt&K1m
template < typename T > 0u,OW
struct result_1 BK+(Uf;g
{ P5&mpl1
typedef typename const_value < typename Left::template result_1 < T > ::result_type > ::value_type result_type; 0O_acO4
} ; x37pj)i/
;t^8lC?>V
template < typename T1, typename T2 > n_8wYiBs(
struct result_2 ue YBD]3'
{ M=HP!hn
typedef typename const_value < typename Left::template result_2 < T1, T2 > ::result_type > ::value_type result_type; DA0{s
} ; pONBF3H8
} ; +N~?_5lv\s
!'ylh8}
b[mAkm?9+1
其中const_value是一个将一个类型转为其非引用形式的trait ig] hY/uT
Y<A593
下面我们来剥离functor中的operator() &n5Lc`
首先operator里面的代码全是下面的形式: =*ZQGM 3w
kX\\t.nH
return l(t) op r(t) 7<'4WHi;@s
return l(t1, t2) op r(t1, t2) vCPiT2G
return op l(t) cqr4P`Oj
return op l(t1, t2) Hg~O0p}[
return l(t) op n'dxa<F2|
return l(t1, t2) op M- f)\`I
return l(t)[r(t)] zsQhydTR
return l(t1, t2)[r(t1, t2)] <