一. 什么是Lambda y
4
wV]1
所谓Lambda,简单的说就是快速的小函数生成。 AfAlDM'
在C++中,STL的很多算法都要求使用者提供一个函数对象。例如for_each函数,会要求用户提供一个表明“行为”的函数对象。以vector<bool>为例,如果想使用for_each对其中的各元素全部赋值为true,一般需要这么一个函数对象, mp'Z.4
Yg<L pjq5X
Ri
#oYPe:8|m
class filler 6D\$K
{ bHKTCPf
public : $yn7XonS
void operator ()( bool & i) const {i = true ;} (yJY/|
} ; U}yq*$N
e7_.Xr~[
@sr~&YhA
这样实现不但麻烦,而且不直观。而如果使用lambda,则允许用户使用一种直观和见解的方式来处理这个问题。以boost.lambda为例,刚才的问题可以这么解决: ^@V;`jsll
o^efeI
gTM*td(~^
$q|-9B
for_each(v.begin(), v.end(), _1 = true ); yv;KKQ
mhNX05D
=K\xE"
那么下面,就让我们来实现一个lambda库。 Yy 8?X9r.
n%S%a>IQj
o){\qhLp
xCQLfXK7
二. 战前分析 {`ghX%M(l
首先要说明的是,我并没有读过boost.lambda或其他任何lambda库的代码,因此如代码有雷同,纯属巧合。 YAdk3y~pL
开始实现以前,首先要分析出大致的实现手法。先让我们来看几段使用Lambda的代码 /g`!Zn8a
& FpoMW
/Kd9UQU
for_each(v.begin(), v.end(), _1 = 1 ); ?~:4O}5Ax
/* --------------------------------------------- */ uGc0Lv4i/
vector < int *> vp( 10 ); 1PN!1= F}
transform(v.begin(), v.end(), vp.begin(), & _1); ke)}JU^"
/* --------------------------------------------- */ @zCp/fo3
sort(vp.begin(), vp.end(), * _1 > * _2); d :vuRK4+
/* --------------------------------------------- */ u\AL`'v
int b = * find_if(v.begin, v.end(), _1 >= 3 && _1 < 5 ); 7WMF8(j5
/* --------------------------------------------- */ nb~592u
for_each(vp.begin(), vp.end(), cout << * _1 << ' \n ' ); "-
?uB Mz
/* --------------------------------------------- */ n1Wo<$#
for_each(vp.begin(), vp.end(), cout << constant( ' \n ' ) << * _1); v[2N-
'8"nXuL-
j[RY
z 0}JiW R
看了之后,我们可以思考一些问题: D#k ~lEPub
1._1, _2是什么? %TeH#%[g>\
显然_1和_2都满足C++对于标识符的要求,可见_1和_2都是对象。 %MM)5MsB
2._1 = 1是在做什么? KU=+ 1,Jf
既然_1是一个对象,那么_1的类必然重载了operator=(int)。那么operator=返回什么呢?该函数所返回的对象被传入for_each的第3个参数,可见其返回了一个函数对象。现在整个流程就很清楚了。_1 = 1调用了operator=,其返回了一个函数对象,该函数对象能够将参数1赋值为1。 9_b_O T
Ok,回答了这两个问题之后,我们的思路就很清晰了。如果要实现operator=,那么至少要实现2个类,一个用于产生_1的对象,另一个用于代表operator=返回的函数对象。 BO,xA -+
yno X=#`
5-RA<d#
三. 动工 V<i_YLYmJe
首先实现一个能够范型的进行赋值的函数对象类: |
9 <+!t\
jX;$g>P
xG1(vn83gq
ri1;i= W
template < typename T > u- }@^Y$M
class assignment Bfu/w
{ VvUP;o&/
T value; zN&m-nrw
public : W,5_i7vr
assignment( const T & v) : value(v) {} X@Bg_9\i
template < typename T2 > [OYSNAs*y
T2 & operator ()(T2 & rhs) const { return rhs = value; } +Ym#!"
} ; E*vh<C
|%g)H,6c
]Om;bmwt
其中operator()被声明为模版函数以支持不同类型之间的赋值。 DP.Y<V)B
然后我们就可以书写_1的类来返回assignment ^
A J_
ILIv43QKM(
A
D%9;KQ8
5|A"YzY#
class holder xqpq|U
{ z^o7&\:
public : -7IRlP&
template < typename T > HLX#RQ
assignment < T > operator = ( const T & t) const &U_T1-UR2
{ mM2DZ^"j(
return assignment < T > (t); FM"[:&>
} 1l s 8 h
} ; oi7Y?hTj
LYke\/ md
+62}//_?
由于该类是一个空类,因此我们可以在其后放心大胆的写上: _/NPXDL
c{3P|O&.
static holder _1; 9hei8L:
Ok,现在一个最简单的lambda就完工了。你可以写 Ov;q]Vn>
?P;=_~X
for_each(v.begin(), v.end(), _1 = 1 ); J6mUU3F9f
而不用手动写一个函数对象。 HBm(l@#.
jG%J.u^k
{ ^Rr:+
%x8vvcO^t
四. 问题分析 >-j([%
虽然基本上一个Lambda已经初步实现出来了,但是仔细想想,问题也是很多的。 XG!^[ZDs
1, 我们现在是把_1和functor看成两个不同的存在,会导致代码的重复。 TPA*z9n+B
2, 目前这个Lambda还无法实现如_1 = 2 = 3这样的链式操作。 [M2xF<r6t
3, 我们没有设计好如何处理多个参数的functor。 |F +n7
下面我们可以对这几个问题进行分析。 s{:Thgv,9
p{x6BVw?>
五. 问题1:一致性 tN;^{O-(V
首先来看看1,合并_1和functor的最佳方法就是把_1本身也变成functor。那么_1的operator()会做什么事情呢?| `0`#Uf_/$
很明显,_1的operator()仅仅应该返回传进来的参数本身。 iSNbbu#
^[VEr"X
struct holder t9r
R>Y9
{ K_fJ{Vc>O
// Flaqgi/j
template < typename T > \rY\wa
T & operator ()( const T & r) const e>Dux
{ E %?>
%h
return (T & )r; QN;GMX5&
} r_MP[]f|0
} ; +4F; m_G6
_^D -nk?
这样的话assignment也必须相应改动: F$S/zh$)0
y]g5S-G
template < typename Left, typename Right > [W99}bi$
class assignment g,B@*2Uj
{ } x
KvN
Left l; @QDUz>_y
Right r; SC--jhDZ
public : USJ4Z
assignment( const Left & l, const Right & r) : l(l), r(r) {} 8l<~zIoO
template < typename T2 > a1x].{
T2 & operator ()(T2 & rhs) const { return l(rhs) = r; } v8TNBsEL
} ; v}=pxWhm
k>=wwPy
同时,holder的operator=也需要改动: >:OP+Vc
zVis"g`
template < typename T > P]7s1kgaS
assignment < holder, T > operator = ( const T & t) const iV:\,<8d
{ AD>/#Ul
return assignment < holder, T > ( * this , t); bYYjP.rcF
} s>=$E~qq
f[q_eY
好,这样holder也成为了一个functor,这为我们以后添加功能节省了很多代码。 (`<B#D;
你可能也注意到,常数和functor地位也不平等。 nv3TxG
?4t~z 1.f
return l(rhs) = r; Ch]q:o4
在这一句中,r没有调用operator()而l调用了。这样以后就要不时的区分常数和functor,是不良的设计。 <bJ~Ol
那么我们仿造holder的做法实现一个常数类: ]UrlFiR
}OSf C~5P
template < typename Tp > G+WCE*
class constant_t [OFT!=.y &
{ t&-c?&FO\;
const Tp t; ~Fo`Pr_
public : I#xhmsF
constant_t( const Tp & t) : t(t) {}
GYonb)F
template < typename T > OkphbAX
const Tp & operator ()( const T & r) const D"K!ELGW
{ u@aM8Na
return t; 2|`~3B)#
} KF7d`bRe
} ; PAiVUGp5[
LNvkC4
该functor的operator()无视参数,直接返回内部所存储的常数。 eTt{wn;6
下面就可以修改holder的operator=了 5;[0Q
Xm6M s<z6
template < typename T >
c70B
assignment < holder, constant_t < T > > operator = ( const T & t) const w$749jGx
{ _X)]/A%@
return assignment < holder, constant_t < T > > ( * this , constant_t < T > (t)); -./Y
} xG(:O@
z]sQ3"cmX
同时也要修改assignment的operator() tAb3ejCo?
O>ZJOKe
template < typename T2 > th=45y"C
T2 & operator ()(T2 & rhs) const { return l(rhs) = r(rhs); } hG3RZN#ejq
现在代码看起来就很一致了。 <4;f?eu
`U;V-
六. 问题2:链式操作 ]xhH:kW4
现在让我们来看看如何处理链式操作。 2Mu(GUe;
其实问题1已经为我们处理掉了大量的问题。如果_1,functor,常量彼此之间不统一为functor,那么链式操作的时候就要时刻小心一个对象是_1还是functor还是常量,会大大增加编码的难度。 eoPoGC
事实上,首先要解决的是,如何知道一个functor的operator()的返回值的类型。遗憾的是,我并没有找到非常自动的办法,因此我们得让functor自己来告诉我们返回值的类型。 mW)"~sA
比较麻烦的是,operator()的返回值一般和其参数的类型相关,而operator()通常是一个模版函数,因此其返回值类型并不能用一个简单的typedef来指定,而必须实现一个trait。 QEEX|WM
现在我们在assignment内部声明一个nested-struct 'YEiT#+/
x_EU.924uY
template < typename T > &0mhO+g
struct result_1 *gI9CVfQl
{ 6uFGq)4p@
typedef typename ref < typename Left::result_1 < T > ::result > ::reference result; ND5E`Va5R
} ; /PkOF((
*JaFt@ x
那么如果参数为T,其返回值类型就为result_1<T>::result。上面代码的ref<T>为一个类型转换类,作用是返回T的引用。不直接加上&符号的原因是如果T本身就是Q的引用Q&,那么Q&&是非法的。因此ref的实现即为: C,u;l~zz
.|K\1qGW0
template < typename T > \)PS&Y8n
struct ref U4Pk^[,p1G
{ $P&27
typedef T & reference; b*a}~1
} ; CjA}-ee
template < typename T > w2tkJcQ3
struct ref < T &> .sUL5`
{ vaZ?>94
typedef T & reference; BimM)4g
} ; a[gN+DX%L
r3.v ^
有了result_1之后,就可以把operator()改写一下: qxD<mZ@-R0
wSs78c=
template < typename T > >2)!w
typename result_1 < T > ::result operator ()( const T & t) const zyI4E\
{ & l~=c2
return l(t) = r(t); =`%%*
} {XYf"ONi
可能大家已经注意到我定义assignment的operator()的返回类型的时候,是直接将其定义为Left的operator()返回类型的引用形式,如果实际上处理的对象的operator=并不是按照常理来声明的,那么这段代码可能就编译不过。这的确是一个很麻烦的事情。实际上,在gcc下,使用typeof关键字可以很容易的得到该类型的operator=的返回类型,就可以让这段代码变得更有通用性。然而为了实现可移植性,我不得不放弃这个诱人的想法。 $Vm J[EF1
同理我们可以给constant_t和holder加上这个result_1。 ~K|o@LK
%P]-wBJw
有了这个result_1,链式操作就简单多了。现在唯一要做的事情就是让所有的functor都重载各种操作符以产生新的functor。假设我们有add和divide两个类,那么 QLTE`t5w3'
_1 / 3 + 5会出现的构造方式是: ZP%Bu2xd
_1 / 3调用holder的operator/ 返回一个divide的对象 NO)vk+
+5 调用divide的对象返回一个add对象。 ?/ s=E+
最后的布局是: L G9#D
Add R7By=Y!t
/ \ 0M>%1*
Divide 5 lc0Z fC
/ \ dnTXx*I:
_1 3 GG_A'eX:I
似乎一切都解决了?不。 ?Qs>L~
你可以想象一下一个完整的Lambda库,它必然能够重载C++几乎所有的操作符。假设其重载了10个操作符,那么至少会有10个代表这些操作符的functor类。大体上来讲,每一种操作符所对应的functor都应当能够由链式操作产生别的任意一种操作符所对应的functor。(例如:*_1 = 2既是由operator*的functor产生operator=的functor)。可想而知这样一共能产生10*10=100种产生方式。这是对编码的一个大挑战。 fKT(.VNq5
如何简化这个问题呢?我们不妨假定,任意一种操作符的functor,都能够产生任意一种操作符的functor,这样,每一种操作符的functor都拥有一样的产生方案。如果某种转换确实是不合法的(例如:A/B=C无论如何也不可能合法),那么在试图产生新functor的时候会出现编译错误。幸好C++的模版是如果不使用就不编译的,因此这种编译错误不会干扰到正常的使用,这正是我们所要的。 k4nA+k<WI`
OK,我们的方法呼之欲出了。既然所有的functor都具有一样的产生方案,那么不如大家都不要实现,等到最后统一的在所有的functor里面加上这么一系列的产生代码吧。例如,如果要添加从某functor XXX到operator=的functor的产生代码: #kGxX@0
8%9OB5?F6
template < typename Right > %K]nX#.B&
assignment < XXX, typename picker_maker < Right > ::result > operator = ( const 0b}lwo,|\
Right & rt) const KBGJB`D*
{ uO-R:MC
return assignment < XXX, typename picker_maker < Right > ::result > ( * this , rt); /h%MWCZWm^
} :hxZ2O?5_
下面对该代码的一些细节方面作一些解释 @)8C
XXX指的是原来的functor的类型,picker_maker<T>是一个类型变换的trait,如果T是一个常量,那么他会返回constant_t<T>,否则返回T本身。 }~5xlg$B<<
因此如果该函数声明在assignment的内部,那么就实现了连等,如果声明在的dereference(解引用)的内部,就允许(*A = B)的行为发生。 K#{E87G(
最后,如何把这些函数塞到各个functor的声明里边呢?当然可以用宏,但是。。。大家都知道这样不好。 ]H<C Rw
除了宏之外还可以用的方式就是继承。我们可以写一个类叫做picker,该类实现了所有的如上的产生函数。然后让所有的functor继承自它。 `24:Eg6r
且慢,也许立刻就有人跳出来说:这样的话那个XXX怎么写呢?这样不是会导致循环依赖么?这样不是会有downcast么? r^6vo6^
正解,让picker做基类确实不是一个好主意。反过来,让picker继承functor却是一个不错的方法。下面是picker的声明: +NEP*mk
&On0)G3Rc
template < class Action > P^LOrLmo8
class picker : public Action j|WaWnl=
{ P6 G/J-
public : Qs{Qg<}
picker( const Action & act) : Action(act) {} 9P)<CD0
// all the operator overloaded zR3Z(^]v
} ; _mL 9G5~r
wh:`4Yw
Picker<T>继承自T,唯一的作用就是给T添加上了各种操作符的重载函数。 jW",'1h<n
现在所有参与行动的functor都要套上一层picker, _1被声明为 picker<holder>, 并且holder中所重载的操作符除了operator()之外全部被移到了picker内。而picker中的操作符重载的返回的functor也必须套上一个picker: L=}UApK
+=@Z5eu
template < typename Right > p:ST$ 1 K
picker < assignment < Action, typename picker_maker < Right > ::result > > operator = ( const Right & rt) const P-`^I`r
{ osX23T~-
return assignment < Action, typename picker_maker < Right > ::result > ( * this , rt); YKvFZH)
} F]?$Q'U
w }2|Do$5
Piker_maker返回的也是picker<T>,或者picker<constant_t<T> > T}]Ao
使用picker还带来一个额外的好处。之前提到picker_maker要区分functor和常量,有了picker,区分的方法就非常简单了:凡是属于picker<T>的都是functor,否则就是常量。 U>x2'B v
.]H]H *wC
template < typename T > struct picker_maker hOMFDfhU
{ L ou4M
typedef picker < constant_t < T > > result; .^.UJo;4G
} ; AQ
7e
template < typename T > struct picker_maker < picker < T > > ^! ZjK-$A<
{ cCV"(Oo[H|
typedef picker < T > result; Nd!2 @?V4
} ; "x$S%:p
)SUN+YV^
下面总的结构就有了: Q84KU8?d
functor专心模拟操作符的行为,并实现一个result_1来告诉别人自己的返回类型。 W{m0z+N[B
picker专心负责操作符之间的产生关系,由它来联系操作符合functor。 W\<#`0tUt
picker<functor>构成了实际参与操作的对象。 O x$|ZEh
至此链式操作完美实现。 =3SL&
:8
#-HN[U?Gs
=\%>O7c,8Y
七. 问题3 lE|T'?/
如何使用多参数的函数对象呢?考虑_1=_2,这个functor必须接受2个参数,因此所产生的assignment对象的operator()必须能接收2个参数。 3Ob"r`
-;`W"&`ss
template < typename T1, typename T2 > ^Q :K$!
??? operator ()( const T1 & t1, const T2 & t2) const '7*=m^pc
{ UXk8nH
return lt(t1, t2) = rt(t1, t2); }5tn
} IfXLnD^||
fF[ g%?w
很明显,这个函数的返回类型会依赖于T1,T2,因此result_1已经无法适用,我们就只好再写一个result_2: dju&Ku
{M~!?#<K
template < typename T1, typename T2 > 8:xQPd?3
struct result_2
B?%D
{ j'J*QK&Q
typedef typename ref < typename Left::result_2 < T1, T2 > ::result > ::reference result; \+AH>I;vO
} ; VYAe!{[
4COf H7Al9
显然,各个functor似乎根本不理会各个参数那个是_1, 那个是_2, 那么最后是怎么选择的呢? a@jP^VVk
这个差事就留给了holder自己。 49zp@a
}\*Sf[EMD
rzBWk
template < int Order > !3&vgvr
class holder; "&+0jfLY+
template <> d|NNIf
class holder < 1 > d<3"$%C
{ 3CHte*NL=
public : QF>[cdl?8
template < typename T > BVNh>^W5B
struct result_1 Ul'G
g
{ )w`Nkx
typedef T & result; Hf-F-~E
} ; %ej"ZeM
template < typename T1, typename T2 > BmJ?VJ}Y
struct result_2 }I`|*6Up
{ 8say"Qz
typedef T1 & result; 4QVd{
} ; M1M]]fT0ME
template < typename T > 8Z!ea3kAT
typename result_1 < T > ::result operator ()( const T & r) const K/,lw~>
{ Le'\x`B
return (T & )r; j&mL]'Zy
} ,RHHNTB("
template < typename T1, typename T2 > A{o{o++
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const v:0i5h&M
{ Ji[w; [qL
return (T1 & )r1; g:clSN,
} '~cEdGD9H
} ; VV4_
>lW*%{|b$^
template <> J@TM>R
class holder < 2 > 3*TS
4xX
{ -&A[{m <,>
public : ~<U3KB
template < typename T > {L eEnh-
struct result_1 %dU}GYL_
{ /YbL{G
)j}
typedef T & result; N9ufTlq
s
} ; ybG)=0
template < typename T1, typename T2 > i=a LC*@
struct result_2 @6!JW(,]\
{ `+o.w#cl
typedef T2 & result; YC_^jRB8n
} ; Vel;t<1
template < typename T > u@EM,o
typename result_1 < T > ::result operator ()( const T & r) const {EUH#':
{ IXN4?=)I
return (T & )r; M5V1j(URE
} g3XAs@
template < typename T1, typename T2 > A!kyga6F5
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const Mt Z(\&~
{ aVYUk7_ <
return (T2 & )r2; ,H?p9L; qp
} jb2:O,+!
} ; {\&"I|dpe
#c>MUC(?s:
h<.[U
$,
新的holder变成了holder<int>, holder<n>的n个参数的operator()会返回第n个参数的值。而_1,_2也相应变为picker<holder<1> >, picker<holder<2> >。 bSghf"aN
现在让我们来看看(_1 = _2)(i. j)是怎么调用的: ,lJ6"J\8.
首先 assignment::operator(int, int)被调用: S8RB0^Q7
Q ?t
return l(i, j) = r(i, j); dmy-}.pqN
先后调用holder<1>::operator()(int, int)和holder<2>::operator()(int, int) k
I~]u
;"
*`
return ( int & )i; j#f&!&G5<&
return ( int & )j; >i%w'uU
最后执行i = j; t>2^!vl
可见,参数被正确的选择了。 | dwxea
VWv0\:,G
ODEXQl}R
wjJ1Psnx
'5U$`Xe1
八. 中期总结 R6XMBYK^
目前的结果是这样的,为了支持一个操作符,我们需要作如下几件事: m4wTg
8LJ
1。 实现一个functor,该functor的operator()要能执行该操作符的语义 ["<(\v9P)
2。 在该functor中实现result_1至result_n,其中n是支持参数的最大值。 jTr4A-"
3。 在picker中实现一个操作符重载,返回该functor '>Y
2lqa
=7Vl{>*1N
A*~1Uz\t
lKUm_; m
%},G(>
\2xBOe-a]
九. 简化 J\'5CG
很明显,要支持一个操作符所要做的工作太多了,而且在每个functor中申明result_1至result_n,可见如果n发生变化,维护的开销极大。 rb'Gve W[
我们现在需要找到一个自动生成这种functor的方法。 @t8kN6.
首先,我们注意到result_x的形式很统一。对于各种操作符,其返回值无非下列几种: O97bgj]
1. 返回值。如果本身为引用,就去掉引用。 })lT fy
+-*/&|^等 YXVJJd$U
2. 返回引用。 3{:<z4>{
=,各种复合赋值等 rcmAVl:$>
3. 返回固定类型。 ;
,<J:%s
各种逻辑/比较操作符(返回bool) ~UC/|t$
4. 原样返回。 zD;]
sk4
operator, Te}yQ= +
5. 返回解引用的类型。 !u}3H|6~
operator*(单目) 1cBhcYv"
6. 返回地址。 EE6|9K>
operator&(单目) bTGK@~
7. 下表访问返回类型。 FraW6T}_
operator[] d$rUxqB.
8. 如果左操作数是一个stream,返回引用,否则返回值 Q'%o;z*
operator<<和operator>> _-J @$d%
sC_UalOC_
OK,这样我们将返回值类型总结为以上8种,就可以将各种result_x从functor中剥离出来了。 /2Lo{v=0[
例如针对第一条,我们实现一个policy类: JlQT5k
=awO63j>
template < typename Left > @:9fS
struct value_return
t} i97 ;
{ 7&1~O#
template < typename T > H#6^-6;/
struct result_1 .Pes{uHg
{ oz6+rM6MY
typedef typename const_value < typename Left::template result_1 < T > ::result_type > ::value_type result_type; i: M*L< +
} ; .00=U;H%`
NJf(,Mr*|
template < typename T1, typename T2 > ]}7rWs[|1
struct result_2 pEj^x[b`^
{ pptM&Y
typedef typename const_value < typename Left::template result_2 < T1, T2 > ::result_type > ::value_type result_type; MlK`sH6
} ; zWs*kTtA
} ; qf`xH"$
` u\z!x'
yCCw<