一. 什么是Lambda JFewOt3
所谓Lambda,简单的说就是快速的小函数生成。 ]{^'{ z$i
在C++中,STL的很多算法都要求使用者提供一个函数对象。例如for_each函数,会要求用户提供一个表明“行为”的函数对象。以vector<bool>为例,如果想使用for_each对其中的各元素全部赋值为true,一般需要这么一个函数对象, Y7vUdCj
b~>kTO
'guXdX]Gu
RUco3fZ
class filler fqpbsM;M]
{ 4&N#d;ErC
public : MkLXMwuQ&
void operator ()( bool & i) const {i = true ;} P|j|0o,8p
} ; H{M7_1T
)cP&c=
}$%j} F{
这样实现不但麻烦,而且不直观。而如果使用lambda,则允许用户使用一种直观和见解的方式来处理这个问题。以boost.lambda为例,刚才的问题可以这么解决: 8L1vtYz
H?oBax:
+{#65z
/]pJ(FFC
for_each(v.begin(), v.end(), _1 = true ); zbY2gq@?
*yl?M<28
N>
7sG(!'"
那么下面,就让我们来实现一个lambda库。 x%_VzqR`
nwS @r
G.")Bg
u:]c
二. 战前分析 w :nYsuF
首先要说明的是,我并没有读过boost.lambda或其他任何lambda库的代码,因此如代码有雷同,纯属巧合。 I-Q@v`
开始实现以前,首先要分析出大致的实现手法。先让我们来看几段使用Lambda的代码 amTeTo]Tg
PaD6||1F
6C'W
for_each(v.begin(), v.end(), _1 = 1 ); {{6D4M|s
/* --------------------------------------------- */ Jn7T5$pJ
vector < int *> vp( 10 ); atiyQuT6Wh
transform(v.begin(), v.end(), vp.begin(), & _1); )d~{gPr.
/* --------------------------------------------- */ \+M6R<Qw
sort(vp.begin(), vp.end(), * _1 > * _2); ~k"r
/* --------------------------------------------- */ o%yfR.M6$
int b = * find_if(v.begin, v.end(), _1 >= 3 && _1 < 5 ); _ sqj~|K
/* --------------------------------------------- */ ;NMv>1fI
for_each(vp.begin(), vp.end(), cout << * _1 << ' \n ' ); Bo,>blspw
/* --------------------------------------------- */ /9pN.E
for_each(vp.begin(), vp.end(), cout << constant( ' \n ' ) << * _1); %<)!]8}P*
&=^YN"=Z
~U r
?kIyo
看了之后,我们可以思考一些问题: oyT`AYa
1._1, _2是什么? K<rv|bJ
显然_1和_2都满足C++对于标识符的要求,可见_1和_2都是对象。 :
E`78
2._1 = 1是在做什么? $fCKK&Wy
既然_1是一个对象,那么_1的类必然重载了operator=(int)。那么operator=返回什么呢?该函数所返回的对象被传入for_each的第3个参数,可见其返回了一个函数对象。现在整个流程就很清楚了。_1 = 1调用了operator=,其返回了一个函数对象,该函数对象能够将参数1赋值为1。 >^6|^rc
Ok,回答了这两个问题之后,我们的思路就很清晰了。如果要实现operator=,那么至少要实现2个类,一个用于产生_1的对象,另一个用于代表operator=返回的函数对象。 ;9CbioO
c>Tf@Aog>
9])Id;+91
三. 动工 f\;w(_
首先实现一个能够范型的进行赋值的函数对象类: $l $p|
v<@3&bot
w/z o
7{Lp/z%r
template < typename T > dl.gCiI
class assignment <z#.J]
{ S zNZY&8
f
T value; })+iAxR
public : 0GlQWRa
assignment( const T & v) : value(v) {} 7W'&v+\
template < typename T2 > eQz.N<f"
T2 & operator ()(T2 & rhs) const { return rhs = value; } ,\?s=D{
} ; Mkh/+f4
fig~z=m
3$?nzKTW\
其中operator()被声明为模版函数以支持不同类型之间的赋值。 @HzK)%@
然后我们就可以书写_1的类来返回assignment m@Dra2Cv'@
D<hX%VJ%M
R;w$_1
blLl1Ak
class holder Jkv!]C
{ Kton$%Li
public : &q&~&j'[
template < typename T > /d+v4GIB
assignment < T > operator = ( const T & t) const sib/~j
{ deQ {
return assignment < T > (t); r$v\ \^?2
} o|u4C {j
} ; %"cOX
Oq$-*N
CIui9XNU
由于该类是一个空类,因此我们可以在其后放心大胆的写上: p$G3<Z&7
NT2XG&$W>
static holder _1; 3ZC@q
#R
A
Ok,现在一个最简单的lambda就完工了。你可以写 ,Ne9x\F
(t){o>l
for_each(v.begin(), v.end(), _1 = 1 ); !rs }83w!
而不用手动写一个函数对象。 ]c v/dY#
nrA 4N1
T+x
/J]A
W\($LD"X
四. 问题分析 Yecdw'BW?
虽然基本上一个Lambda已经初步实现出来了,但是仔细想想,问题也是很多的。 {sxdDl
1, 我们现在是把_1和functor看成两个不同的存在,会导致代码的重复。 C=CZtjUt
2, 目前这个Lambda还无法实现如_1 = 2 = 3这样的链式操作。 #D#kw*c
3, 我们没有设计好如何处理多个参数的functor。 C?k\5AzT
下面我们可以对这几个问题进行分析。 amq,^
<& 3[|Ca
五. 问题1:一致性 [ #ih
o(/
首先来看看1,合并_1和functor的最佳方法就是把_1本身也变成functor。那么_1的operator()会做什么事情呢?| fN@ZJ~F%j
很明显,_1的operator()仅仅应该返回传进来的参数本身。 P*i'uN
<2oMk#Ng^
struct holder & kVa*O
{ Qn|8Ic` *
// G)^/#d#&
template < typename T > skXzck
T & operator ()( const T & r) const {0lu>?<
{ @-L\c>rqT
return (T & )r; q sUBvq
} FA>.1EI
} ; n&o"RE 0~0
t*; KxQ+'?
这样的话assignment也必须相应改动: &^K(9"
:Tv>)N
template < typename Left, typename Right > daP_Kz/2K
class assignment 7x77s
{ `\|@w@f|;
Left l; Nmd{C(^o
Right r; St(jrZb
public : $&qLrKJ
assignment( const Left & l, const Right & r) : l(l), r(r) {}
B|V!=r1%
template < typename T2 > r\#nBoo(
T2 & operator ()(T2 & rhs) const { return l(rhs) = r; } ZXL'R|?
} ; gG@4MXq.
?w!8;xS8
同时,holder的operator=也需要改动: ~NPhVlT
6`iYIXnz
template < typename T > cHVJ7yAZI
assignment < holder, T > operator = ( const T & t) const `k*;%}X\
{ `#w#!@s#@
return assignment < holder, T > ( * this , t); 2@?X>,
} (,t[`z
GRJ6|T$!?$
好,这样holder也成为了一个functor,这为我们以后添加功能节省了很多代码。 VwRZgL
你可能也注意到,常数和functor地位也不平等。 E%;$vj'2
!Yr9N4
return l(rhs) = r; ,;5%&T
在这一句中,r没有调用operator()而l调用了。这样以后就要不时的区分常数和functor,是不良的设计。 mn=b&{')e
那么我们仿造holder的做法实现一个常数类: TDbSK&w :s
@)0
template < typename Tp > -9.lFuI
class constant_t $j(d`@.DN~
{ hr&&b3W3p
const Tp t; T)%6"rPL3!
public : livKiX`
constant_t( const Tp & t) : t(t) {} 63%V_B|
template < typename T > wsQ],ZE
const Tp & operator ()( const T & r) const ,C"6@/:l
{ }:YL'$:5!
return t; j24DL+
} LLT6*up$
} ; !'rdHSy
,Y6]x^W
该functor的operator()无视参数,直接返回内部所存储的常数。 7sQHz.4
下面就可以修改holder的operator=了 us ~cIGm
rM,f7hm[S*
template < typename T > '(C+qwdRv
assignment < holder, constant_t < T > > operator = ( const T & t) const AX%}ip[PC
{ ,52Lm=n
return assignment < holder, constant_t < T > > ( * this , constant_t < T > (t)); Tn/Z s|
} Cse`MP
?>{u@tYL
同时也要修改assignment的operator() T@{ab1KV
Y 'm;xA
template < typename T2 > ]\ !ka/%
T2 & operator ()(T2 & rhs) const { return l(rhs) = r(rhs); } +6l#hO7h
现在代码看起来就很一致了。 P_0[spmFU
)eT>[['fm
六. 问题2:链式操作 hu} vYA7ZH
现在让我们来看看如何处理链式操作。 :j .:t
其实问题1已经为我们处理掉了大量的问题。如果_1,functor,常量彼此之间不统一为functor,那么链式操作的时候就要时刻小心一个对象是_1还是functor还是常量,会大大增加编码的难度。 tY]?2u%)
事实上,首先要解决的是,如何知道一个functor的operator()的返回值的类型。遗憾的是,我并没有找到非常自动的办法,因此我们得让functor自己来告诉我们返回值的类型。 N>YSXh`W`y
比较麻烦的是,operator()的返回值一般和其参数的类型相关,而operator()通常是一个模版函数,因此其返回值类型并不能用一个简单的typedef来指定,而必须实现一个trait。 ?;htK_E\*
现在我们在assignment内部声明一个nested-struct J5F@<vi
DnJ `]r
template < typename T > l'_]0%o]
struct result_1 Nu?A>Q
{ 7FPSBvU#/
typedef typename ref < typename Left::result_1 < T > ::result > ::reference result; &L5
)v\z
} ; XEbVsw
Al6%RFt
那么如果参数为T,其返回值类型就为result_1<T>::result。上面代码的ref<T>为一个类型转换类,作用是返回T的引用。不直接加上&符号的原因是如果T本身就是Q的引用Q&,那么Q&&是非法的。因此ref的实现即为: 3u[8;1}7Q
!QvmzuK
template < typename T > T fkGkVR
struct ref P(Rl/eyRM
{ W|Sab$h
typedef T & reference; eR>8V8@
} ; b/qK/O8J
template < typename T > vdvnwzp!l
struct ref < T &> Kr'? h'F
{ %Vltc4QU
typedef T & reference; ; U7P{e05
} ; i.7_ i78\"
h%$^s0w
有了result_1之后,就可以把operator()改写一下: H"^9g3U
f OR9 N/
template < typename T > u&c%L0)E&
typename result_1 < T > ::result operator ()( const T & t) const ?Y
-;781
{ axt;}8
return l(t) = r(t); [jlum>K
} ]NBx5m+y@i
可能大家已经注意到我定义assignment的operator()的返回类型的时候,是直接将其定义为Left的operator()返回类型的引用形式,如果实际上处理的对象的operator=并不是按照常理来声明的,那么这段代码可能就编译不过。这的确是一个很麻烦的事情。实际上,在gcc下,使用typeof关键字可以很容易的得到该类型的operator=的返回类型,就可以让这段代码变得更有通用性。然而为了实现可移植性,我不得不放弃这个诱人的想法。 <yX u!
同理我们可以给constant_t和holder加上这个result_1。 GRYw_}Aa
Rvu5#_P
有了这个result_1,链式操作就简单多了。现在唯一要做的事情就是让所有的functor都重载各种操作符以产生新的functor。假设我们有add和divide两个类,那么 K-\wx5#l/
_1 / 3 + 5会出现的构造方式是: <`=Kt[_BQ
_1 / 3调用holder的operator/ 返回一个divide的对象 ~7KH/%Z-
+5 调用divide的对象返回一个add对象。 wG7>2*(
最后的布局是: @ :PMb Ub
Add :x[()J~N
/ \ Ri`6X_xU
Divide 5 Mb[4_Dc
/ \ @$^4Av-
_1 3 $ .$nv~f
似乎一切都解决了?不。 5EVypw?]x
你可以想象一下一个完整的Lambda库,它必然能够重载C++几乎所有的操作符。假设其重载了10个操作符,那么至少会有10个代表这些操作符的functor类。大体上来讲,每一种操作符所对应的functor都应当能够由链式操作产生别的任意一种操作符所对应的functor。(例如:*_1 = 2既是由operator*的functor产生operator=的functor)。可想而知这样一共能产生10*10=100种产生方式。这是对编码的一个大挑战。 hZ>m:es
如何简化这个问题呢?我们不妨假定,任意一种操作符的functor,都能够产生任意一种操作符的functor,这样,每一种操作符的functor都拥有一样的产生方案。如果某种转换确实是不合法的(例如:A/B=C无论如何也不可能合法),那么在试图产生新functor的时候会出现编译错误。幸好C++的模版是如果不使用就不编译的,因此这种编译错误不会干扰到正常的使用,这正是我们所要的。 KWjhkRK4]
OK,我们的方法呼之欲出了。既然所有的functor都具有一样的产生方案,那么不如大家都不要实现,等到最后统一的在所有的functor里面加上这么一系列的产生代码吧。例如,如果要添加从某functor XXX到operator=的functor的产生代码: g9JZ#B gZ
<EgJm`V
template < typename Right > {_*G"A 9
assignment < XXX, typename picker_maker < Right > ::result > operator = ( const MG.c`t/w
Right & rt) const "|dhmV[;
{ psmDGSm,&
return assignment < XXX, typename picker_maker < Right > ::result > ( * this , rt); Or?c21un
} )V>OND
下面对该代码的一些细节方面作一些解释 #[x*0K-h
XXX指的是原来的functor的类型,picker_maker<T>是一个类型变换的trait,如果T是一个常量,那么他会返回constant_t<T>,否则返回T本身。 R\&z3<-S
因此如果该函数声明在assignment的内部,那么就实现了连等,如果声明在的dereference(解引用)的内部,就允许(*A = B)的行为发生。 #"!ga)a%L
最后,如何把这些函数塞到各个functor的声明里边呢?当然可以用宏,但是。。。大家都知道这样不好。 XI[n!)3
除了宏之外还可以用的方式就是继承。我们可以写一个类叫做picker,该类实现了所有的如上的产生函数。然后让所有的functor继承自它。 +:ms`Sr>
且慢,也许立刻就有人跳出来说:这样的话那个XXX怎么写呢?这样不是会导致循环依赖么?这样不是会有downcast么? }PBL
正解,让picker做基类确实不是一个好主意。反过来,让picker继承functor却是一个不错的方法。下面是picker的声明: n!~ $Z/
f1'X<VA
template < class Action > 8'zl\:@N
class picker : public Action 65O 8?I
{ T6\]*mlr
public : B@ufrQ#Y.
picker( const Action & act) : Action(act) {} n.'Ps+G(
// all the operator overloaded <Fx%P:d
} ; CEw%_U@8
{.e+?V2>_
Picker<T>继承自T,唯一的作用就是给T添加上了各种操作符的重载函数。 sk8DW
现在所有参与行动的functor都要套上一层picker, _1被声明为 picker<holder>, 并且holder中所重载的操作符除了operator()之外全部被移到了picker内。而picker中的操作符重载的返回的functor也必须套上一个picker: NV(jp'i~
4t%Lo2v!X%
template < typename Right > d&|5Rk
~
picker < assignment < Action, typename picker_maker < Right > ::result > > operator = ( const Right & rt) const \P":V
{ IOC$jab@
return assignment < Action, typename picker_maker < Right > ::result > ( * this , rt); eE
.wnn
} v_U/0
0
gk?H@b*
Piker_maker返回的也是picker<T>,或者picker<constant_t<T> > )l
m7ly8a|
使用picker还带来一个额外的好处。之前提到picker_maker要区分functor和常量,有了picker,区分的方法就非常简单了:凡是属于picker<T>的都是functor,否则就是常量。 [) >Yp-n
ZQ`4'|"
template < typename T > struct picker_maker )OFN0'
{ I!9>"s12
typedef picker < constant_t < T > > result; kiyKL:6D|
} ; ju;OQC~[L]
template < typename T > struct picker_maker < picker < T > > gs i2
{ VE*`Ji
typedef picker < T > result; D'ZUbAh!
} ; 1GkoE
GoX<d{
下面总的结构就有了: jz;{,F
functor专心模拟操作符的行为,并实现一个result_1来告诉别人自己的返回类型。 NPO!J^^
picker专心负责操作符之间的产生关系,由它来联系操作符合functor。 *w^!\
picker<functor>构成了实际参与操作的对象。 | ~>7_:
至此链式操作完美实现。 Glx{Zu=
jZmL7
V
vNdX
七. 问题3 Z]2z*XD
如何使用多参数的函数对象呢?考虑_1=_2,这个functor必须接受2个参数,因此所产生的assignment对象的operator()必须能接收2个参数。 Oi~Dio_?
^e_uprZWm
template < typename T1, typename T2 > aA>!p{/x
??? operator ()( const T1 & t1, const T2 & t2) const /5epDDP-t5
{ &Y9%Y/Y
return lt(t1, t2) = rt(t1, t2); ume70ap}m
} IS[q'Cv*
Uh1UZ
r
很明显,这个函数的返回类型会依赖于T1,T2,因此result_1已经无法适用,我们就只好再写一个result_2: 8
ks\-38n1
^jpQfD e6
template < typename T1, typename T2 > ER&\2,fZ
struct result_2 G i(
{ 6kR3[]:16v
typedef typename ref < typename Left::result_2 < T1, T2 > ::result > ::reference result; YaQ5Z-c
} ; fo>_*6i74
fh1-]$z`~
显然,各个functor似乎根本不理会各个参数那个是_1, 那个是_2, 那么最后是怎么选择的呢? /']`}*d
这个差事就留给了holder自己。 N(J#<;!yb
h;#^?v!+
?/@XJcm+
template < int Order > t(.vX
class holder; I'n}6D.M
template <> =)iAU/*N
class holder < 1 > "9*MSsU
{ e_rEu'[av
public : /yUKUXi
template < typename T > /9D
mK%d
struct result_1 (&V*~OR
{ tv`c"Pb
typedef T & result; z([HGq5
} ; ,*x/L?.Z!
template < typename T1, typename T2 > LKZ<\%
X
struct result_2 %|R]nB
{ wJgGw5
typedef T1 & result; fcohYo5mh
} ; KNP^k$=)3c
template < typename T > q/@r#
typename result_1 < T > ::result operator ()( const T & r) const H#nJWe_9A
{ &!'R'{/?X
return (T & )r; +zo\#8*0MF
} jzi^OI7
template < typename T1, typename T2 > M#8_Qbvfk
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const JH2-'
{ ]D2d=\
return (T1 & )r1; fv*
$=m
} p>T
} ; |x _jpR
2*n~r
template <> mpIR: Im
class holder < 2 > 7fT_]H8
{ +^rt48${ y
public : H2+Ijn19E
template < typename T > K}whqe]j
struct result_1 dg0WH_#
{ E(*CEW.V*
typedef T & result; -"2%+S{
} ; FiXE0ZI$0q
template < typename T1, typename T2 > YXU2UIY<~
struct result_2 bQ0+Y?,+/
{ !0KNA1w,
typedef T2 & result; dg(sRTi{
} ; ^O"o-3dte
template < typename T > LTb#1JC
typename result_1 < T > ::result operator ()( const T & r) const THCvcU?X
{ o6:]Hvqjr
return (T & )r; x IL]Y7HWM
} b'`C<Rk
template < typename T1, typename T2 > [4PiQyr
typename result_2 < T1, T2 > ::result operator ()( const T1 & r1, const T2 & r2) const ;_=dB[M
{ 5'wFZ=>vMt
return (T2 & )r2; N=!k2+
} )fke;Y0
} ; es$<Vkbp
'|&?$g(\h
+' .o
新的holder变成了holder<int>, holder<n>的n个参数的operator()会返回第n个参数的值。而_1,_2也相应变为picker<holder<1> >, picker<holder<2> >。 _2}/rwVg
现在让我们来看看(_1 = _2)(i. j)是怎么调用的: OZ<fQf.Gh}
首先 assignment::operator(int, int)被调用: iVM% ]\
O&dh<
return l(i, j) = r(i, j); Ff[GR$m
先后调用holder<1>::operator()(int, int)和holder<2>::operator()(int, int) 7U&<{U<
1w 9zl}
return ( int & )i; Ui"3'OU'
return ( int & )j; _Vt
CC/
最后执行i = j; PiNf;b^9
可见,参数被正确的选择了。 .;:dG
-x>2Wb~%
rGQ([e
d}^hZ8k|
0 \o5+
八. 中期总结 <M,=(p{
目前的结果是这样的,为了支持一个操作符,我们需要作如下几件事: ]L\]Ll;
1。 实现一个functor,该functor的operator()要能执行该操作符的语义 z{U^j:A
2。 在该functor中实现result_1至result_n,其中n是支持参数的最大值。
X;dUlSi
3。 在picker中实现一个操作符重载,返回该functor [](] "r
Uee$5a>(
'W p~8}i@
5^{).fig
hx}X=7w
.DhB4v&
九. 简化 05YsLNh
很明显,要支持一个操作符所要做的工作太多了,而且在每个functor中申明result_1至result_n,可见如果n发生变化,维护的开销极大。 mk3,ke8
我们现在需要找到一个自动生成这种functor的方法。 ebf/cCh
首先,我们注意到result_x的形式很统一。对于各种操作符,其返回值无非下列几种: gt(!I^LHYc
1. 返回值。如果本身为引用,就去掉引用。 QM=Y}
+-*/&|^等 8iUKG
2. 返回引用。 {jEEAH)
=,各种复合赋值等 FBA th
!E
3. 返回固定类型。 G,@Jo[e
各种逻辑/比较操作符(返回bool) kcl Z+E
4. 原样返回。 AaDMX,
operator, j<d,7
5. 返回解引用的类型。 p3cb_
operator*(单目) E
qt\It9
6. 返回地址。 F@-8J?Hl:
operator&(单目) BvpUcICJ
7. 下表访问返回类型。 i[n3ILn
operator[] M4L<u,\1s
8. 如果左操作数是一个stream,返回引用,否则返回值 -N1X=4/fg
operator<<和operator>> ?#/~BZR!
v}uzUY
OK,这样我们将返回值类型总结为以上8种,就可以将各种result_x从functor中剥离出来了。 207h$a,
例如针对第一条,我们实现一个policy类: n,b6|Y0
S 0mt8/ M
template < typename Left > j
k/-7/r
struct value_return >d^DN;p
{ 0@}:`OynX
template < typename T > ,o6,(jJU
struct result_1 '6f)^DYA'?
{ ;^so;>F
typedef typename const_value < typename Left::template result_1 < T > ::result_type > ::value_type result_type; n6#z{,W<3
} ; zkHyx[L
B3&ETi5NTU
template < typename T1, typename T2 > D#d
\1g
struct result_2 Wf-P a9
{ Q6%Pp_$k
typedef typename const_value < typename Left::template result_2 < T1, T2 > ::result_type > ::value_type result_type; NxN~"bfh
} ; )*9,H|2nS
} ; lQ?_1H~4=
;% l0Ml>
_?;74VWA
其中const_value是一个将一个类型转为其非引用形式的trait fI-f Gx
Eyg F,>.4
下面我们来剥离functor中的operator() v=?/c-J*
首先operator里面的代码全是下面的形式: wRe2sjM
Ca#T?HL
return l(t) op r(t) &