发信人: shifan (家没有豚豚 T.T), 板面: C++
标 题: 如何实现Lambda[第二部分]
发信站: 飘渺水云间 (Thu Jun 8 23:30:20 2006), 转信
章节:
八:第一部分的小结
九:简化,如何减少Lambda代码的冗余和依赖性
十:bind的实现
十一:实现phoenix
八. 中期总结
目前的结果是这样的,为了支持一个操作符,我们需要作如下几件事:
1。 实现一个functor,该functor的operator()要能执行该操作符的语义
2。 在该functor中实现result_1至result_n,其中n是支持参数的最大值。
3。 在picker中实现一个操作符重载,返回该functor
九. 简化
很明显,要支持一个操作符所要做的工作太多了,而且在每个functor中申明result_1至
result_n,可见如果n发生变化,维护的开销极大。
我们现在需要找到一个自动生成这种functor的方法。
首先,我们注意到result_x的形式很统一。对于各种操作符,其返回值无非下列几种:
1. 返回值。如果本身为引用,就去掉引用。
+-*/&|^等
2. 返回引用。
=,各种复合赋值等
3. 返回固定类型。
各种逻辑/比较操作符(返回bool)
4. 原样返回。
operator,
5. 返回解引用的类型。
operator*(单目)
6. 返回地址。
operator&(单目)
7. 下表访问返回类型。
operator[]
8. 如果左操作数是一个stream,返回引用,否则返回值
operator<<和operator>>
OK,这样我们将返回值类型总结为以上8种,就可以将各种result_x从functor中剥离出来了
。
例如针对第一条,我们实现一个policy类:
template < typename Left >
struct value_return
{
template < typename T >
struct result_1
{
typedef typename const_value < typename Left::template
result_1 < T > ::result_type > ::value_type result_type;
} ;
template < typename T1, typename T2 >
struct result_2
{
typedef typename const_value < typename Left::template result_2 < T1,
T2 > ::result_type > ::value_type result_type;
} ;
} ;
其中const_value是一个将一个类型转为其非引用形式的trait
下面我们来剥离functor中的operator()
首先operator里面的代码全是下面的形式:
return l(t) op r(t)
return l(t1, t2) op r(t1, t2)
return op l(t)
return op l(t1, t2)
return l(t) op
return l(t1, t2) op
return l(t)[r(t)]
return l(t1, t2)[r(t1, t2)]
很自然的,我们会想到用函数替代这种操作符行为以获得更加一致的形式:
单目: return f(l(t), r(t));
return f(l(t1, t2), r(t1, t2));
双目: return f(l(t));
return f(l(t1, t2));
下面就是f的实现,以operator/为例
struct meta_divide
{
template < typename T1, typename T2 >
static ret execute( const T1 & t1, const T2 & t2)
{
return t1 / t2;
}
} ;
这个工作可以让宏来做:
#define DECLARE_META_BIN_FUNC(op, desc, ret) struct meta_##desc{\
template < typename T1, typename T2 > \
static ret execute( const T1 & t1, const T2 & t2) { return ((T1 & )t1) op
((T2 & )t2);} };
以后可以直接用
DECLARE_META_BIN_FUNC(/, divide, T1)
来申明meta_divide。同样还可以申明宏DECLARE_META_UNY_PRE_FUNC和
DECLARE_META_UNY_POST_FUNC来产生单目前缀和后缀操作符的函数
(ps.我本坚持该lambda实现不使用宏的,但是在这种小剂量的又很一致的代码面前,使用
宏实在是很诱人。。。)
下面就是要把operator()和result_x拼凑起来,形成一个我们要的functor,下面是一个单目
的functor的实现体
template < typename Left, typename Right, typename Rettype, typename FuncType >
class unary_op : public Rettype
{
Left l;
public :
unary_op( const Left & l) : l(l) {}
template < typename T >
typename Rettype::template result_1 < T > ::result_type operator ()( const
T & t) const
{
return FuncType::execute(l(t));
}
template < typename T1, typename T2 >
typename Rettype::template result_2 < T1, T2 > ::result_type
operator ()( const T1 & t1, const T2 & t2) const
{
return FuncType::execute(l(t1, t2));
}
} ;
同样还可以申明一个binary_op
template < typename Left, typename Right, typename Rettype, typename FuncType >
class binary_op : public Rettype
{
Left l;
Right r;
public :
binary_op( const Left & l, const Right & r) : l(l), r(r) {}
template < typename T >
typename Rettype::template result_1 < T > ::result_type operator ()( const
T & t) const
{
return FuncType::execute(l(t), r(t));
}
template < typename T1, typename T2 >
typename Rettype::template result_2 < T1, T2 > ::result_type
operator ()( const T1 & t1, const T2 & t2) const
{
return FuncType::execute(l(t1, t2), r(t1, t2));
}
} ;
很完美不是么,unary_op/binary_op继承了Rettype, 也就拥有了该类所定一个全部
result_x, 同时使用FuncType来执行运算符操作,很漂亮
比如要支持操作符operator+,则需要写一行
DECLARE_META_BIN_FUNC(+, add, T1)
那么binary_op<Left, Right, value_return, meta_add>就自然是operator+(双目)的
functor,不需要自己手动实现。
停!不要陶醉在这美妙的幻觉中!
如果把这段代码拿到VC7或VC8下编译,你会得到很有趣的结果。。。
好了,这不是我们的错,但是确实我们应该解决它。
这实际上是vc的bug,解决方法是不要去使用typename Rettype::template result_2<T1,
T2>::result_type这样的形式。(感谢vbvan)
下面是修改过的unary_op
template < typename Left, typename OpClass, typename RetType >
class unary_op
{
Left l;
public :
unary_op( const Left & l) : l(l) {}
template < typename T >
struct result_1
{
typedef typename RetType::template result_1 < T > ::result_type
result_type;
} ;
template < typename T1, typename T2 >
struct result_2
{
typedef typename RetType::template result_2 < T1, T2 > ::result_type
result_type;
} ;
template < typename T1, typename T2 >
typename result_2 < T1, T2 > ::result_type operator ()( const T1 & t1, const
T2 & t2) const
{
return OpClass::execute(lt(t1, t2));
}
template < typename T >
typename result_1 < T > ::result_type operator ()( const T & t) const
{
return OpClass::execute(lt(t));
}
} ;
该方法避免直接使用RetType的result_x,而自己申明一个对应的result_x做一次中转,虽
然其实毫无意义,却恰好避开了vc的bug
好啦,现在才真正完美了。
现在在picker里面就可以这么添加了:
template < typename Right >
picker < binary_op < Action, typename picker_maker < Right > ::result_type,
ref_return < Action > , meta_add_assign > > operator += ( const Right & rt) const
{
return binary_op < Action, typename picker_maker < Right > ::result_type,
ref_return < Action > , meta_add_assign > ( * this , rt);
}
有点长不是么?不过实际代码量减少了很多,而且此后如果支持的参数上限发生变化,我们
就只需要修改binary_op和unary_op就行了。
十. bind
既然都做到这份上了,我们顺便把bind也做了吧,其实事情已经变得很简单了。
先来分析一下一段例子
int foo( int x, int y) { return x - y;}
bind(foo, _1, constant( 2 )( 1 ) // return -1
bind(foo, _2, _1)( 3 , 6 ) // return foo(6, 3) == 3
可见bind是一系列重载函数,返回某种functor,该functor的执行就是执行传进bind的函数
指针并正确的确定参数。
我们来写个简单的。
首先要知道一个函数的返回类型,我们使用一个trait来实现:
对于函数对象类的版本:
template < typename Func >
struct functor_trait
{
typedef typename Func::result_type result_type;
} ;
对于无参数函数的版本:
template < typename Ret >
struct functor_trait < Ret ( * )() >
{
typedef Ret result_type;
} ;
对于单参数函数的版本:
template < typename Ret, typename V1 >
struct functor_trait < Ret ( * )(V1) >
{
typedef Ret result_type;
} ;
对于双参数函数的版本:
template < typename Ret, typename V1, typename V2 >
struct functor_trait < Ret ( * )(V1, V2) >
{
typedef Ret result_type;
} ;
等等。。。
然后我们就可以仿照value_return写一个policy
template < typename Func >
struct func_return
{
template < typename T >
struct result_1
{
typedef typename functor_trait < Func > ::result_type result_type;
} ;
template < typename T1, typename T2 >
struct result_2
{
typedef typename functor_trait < Func > ::result_type result_type;
} ;
} ;
最后一个单参数binder就很容易写出来了
template < typename Func, typename aPicker >
class binder_1
{
Func fn;
aPicker pk;
public :
template < typename T >
struct result_1
{
typedef typename func_return < Func > ::template
result_1 < T > ::result_type result_type;
} ;
template < typename T1, typename T2 >
struct result_2
{
typedef typename func_return < Func > ::template result_2 < T1,
T2 > ::result_type result_type;
} ;
binder_1(Func fn, const aPicker & pk) : fn(fn), pk(pk) {}
template < typename T >
typename result_1 < T > ::result_type operator ()( const T & t) const
{
return fn(pk(t));
}
template < typename T1, typename T2 >
typename result_2 < T1, T2 > ::result_type operator ()( const T1 & t1, const
T2 & t2) const
{
return fn(pk(t1, t2));
}
} ;
一目了然不是么?
最后实现bind
template < typename Func, typename aPicker >
picker < binder_1 < Func, aPicker > > bind( const Func fn, const aPicker & pk)
{
return binder_1 < Func, aPicker > (fn, pk);
}
2个以上参数的bind可以同理实现。
另外还可以照样实现一系列binder来绑定类成员函数/变量,手法雷同,就不详细介绍了。
十一. phoenix
Boost.phoenix可能知道的人不多,让我们来看一段代码吧:
for_each(v.begin(), v.end(),
(
do_
[
cout << _1 << " , "
]
.while_( -- _1),
cout << var( " \n " )
)
);
是不是华丽的让人撞墙?其实这个比想象的好实现的多。还是照惯例分析一下吧:
首先do_很明显是个对象,该对象重载了operator[],接受一个functor作为参数,并返回另
一个对象,该对象有一个成员函数while_,同样接受一个functor作为参数,并返回一个
functor, 最后2个functor用operator, 生成一个新的functor
operator,的实现这里略过了,请参照前面的描述。
那么我们就照着这个思路来实现吧:
template < typename Cond, typename Actor >
class do_while
{
Cond cd;
Actor act;
public :
template < typename T >
struct result_1
{
typedef int result_type;
} ;
do_while( const Cond & cd, const Actor & act) : cd(cd), act(act) {}
template < typename T >
typename result_1 < T > ::result_type operator ()( const T & t) const
{
do
{
act(t);
}
while (cd(t));
return 0 ;
}
} ;
这就是最终的functor,我略去了result_2和2个参数的operator().
代码很清晰,但是还是让我来解释一下为什么要用int作为返回类型。
其实对于do-while语义,返回类型是无意义的,然而将其定义为void会影响在某些情况下
return的简洁性,因为return一个void是不合法的。
因此我们将其定为int,并返回0,这样减少了其它地方编码的复杂度。
下面就是产生这个functor的类:
template < typename Actor >
class do_while_actor
{
Actor act;
public :
do_while_actor( const Actor & act) : act(act) {}
template < typename Cond >
picker < do_while < Cond, Actor > > while_( const Cond & cd) const ;
} ;
简单吧,注意到这个while_函数,它自动的生成了一个do_while对象。
最后,是那个do_
class do_while_invoker
{
public :
template < typename Actor >
do_while_actor < Actor > operator [](Actor act) const
{
return do_while_actor < Actor > (act);
}
} do_;
好啦,现在明白do_[xxx].while_(xxx)是怎么工作的吧?
同样的,我们还可以做if_, while_, for_, switch_等。
最后来说说怎么处理break和continue
显然break的语义超出了我们的能力范围,然而却是有一个东西很适合模拟其行为,那就是
异常。
具体实现手法这里就不罗嗦了。
--
You well 撒法!You well all 撒法!
※ 来源:·飘渺水云间 freecity.cn·[FROM: shifan]
posted on 2006-07-15 15:32
shifan3 阅读(954)
评论(0) 编辑 收藏 引用 所属分类:
template 、
Boost 、
C++