發(fā)信人: shifan (家沒有豚豚 T.T), 板面: C++
標 題: 如何實現Lambda[第二部分]
發(fā)信站: 飄渺水云間 (Thu Jun 8 23:30:20 2006), 轉信
章節(jié):
八:第一部分的小結
九:簡化,如何減少Lambda代碼的冗余和依賴性
十:bind的實現
十一:實現phoenix
八. 中期總結
目前的結果是這樣的,為了支持一個操作符,我們需要作如下幾件事:
1。 實現一個functor,該functor的operator()要能執(zhí)行該操作符的語義
2。 在該functor中實現result_1至result_n,其中n是支持參數的最大值。
3。 在picker中實現一個操作符重載,返回該functor
九. 簡化
很明顯,要支持一個操作符所要做的工作太多了,而且在每個functor中申明result_1至
result_n,可見如果n發(fā)生變化,維護的開銷極大。
我們現在需要找到一個自動生成這種functor的方法。
首先,我們注意到result_x的形式很統(tǒng)一。對于各種操作符,其返回值無非下列幾種:
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來執(zhí)行運算符操作,很漂亮
比如要支持操作符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);
}
有點長不是么?不過實際代碼量減少了很多,而且此后如果支持的參數上限發(fā)生變化,我們
就只需要修改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的執(zhí)行就是執(zhí)行傳進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 閱讀(991)
評論(0) 編輯 收藏 引用 所屬分類:
template 、
Boost 、
C++