c++11模擬boost元占位符
准備實現meta programming的fold函數,發現自己缺少占位符實現,這樣傳入fold的transform op類(元函數)都不得不另外寫個外覆類,其實我覺得沒啥不好,簡單直接,說實話干擾什麼的沒那麼嚴重,一個功能塊裡能用fold的地方能有幾次?但動了占位符這個念頭,就想嘗試實現一下。
看一下實際情景:
template<typename TList, typename Init, class TransformOp>
struct fold_s {};
我們可能會希望把push_back作為運算子傳入fold_s中,從而實現循環迭代TList的每一個元素,對其應用push_back。如:
using type = fold_s<typelist<int, float, char>, nullist, push_back>::type;
問題是,push_back並不是一個類,只是一個聲明,push_back<somelist, t>如此才是一個真正的類,而一般只有類才能作為實參傳入。
最直接的做法是寫個外覆類:
復制代碼
struct push_back_wrap
{
template<typename TList, typename T>
struct apply
{
using type = typename mpl::push_back<TList, T>::type;
};
};
復制代碼
傳入fold_s然後調用apply:
復制代碼
template<typename TList, typename Init, class TransformOp>
struct fold_s
{
using sometype = typename TransformOp::apply<TList, T>::type;
};
using type = fold_s<typelist<int, float, char>, nullist, push_back>::type;
復制代碼
我們知道很多函數語言的一個特征就是延遲計算。此處push_back_wrap中的嵌套類apply,使得push_back_wrap也具有延遲的特性,類型計算直到fold_s真正應用apply時
才發生。這就是meta programming中實現lambada的手法。缺點是我們必須要在使用lambda元類的地方都默認假設apply存在。相比於它的強大功能,因為c++ mpl的
限制導致這個小不便,我們就忍忍吧。
以上說明了一個占位符將要應用的情境。下面就開始no zuo no die的處理吧。其實就是有些人不希望每次用flod_s時都要寫個外覆類,他們希望當flod_s需要傳入push_back
時就直接傳入push_back,好看好記些。很明顯那只能傳入一個push_back的特化了。
fold< vector<int, float, char>, vector<>, push_back<_1, _2> >::type;
上邊的_1,_2就是占位符了。push_back<_1, _2>就是我們所討論的特化的。顯然_1, _2是個類,在上述語句中分別指vector<>,int,總之占位符將指定你需要指定的位置。
這個特化既然取代了外覆類,那它必然提供了相似的功能。也就是push_back必然是個類型延遲的元函數類,它具有類似下面的結構:
復制代碼
struct push_back<...>
{
struct apply
{
type...
};
};
復制代碼
那麼在fold_s內當調用push_back::apply時,顯然push_back必須要具備從參數列表中挑選指定參數的能力,自然的,這個任務就交給_1,_2占位符了。實現的辦法你可以
去查看boost mpl庫的做法,也可使用我下邊的做法(需要c++11支持):
復制代碼
#ifndef HI_MPL_PLACEHOLDERS_H_INCLUDED
#define HI_MPL_PLACEHOLDERS_H_INCLUDED
//////////////////////////////////////////////////////////////////////
namespace hi { namespace mpl {
//surport palceholders is too painful
namespace placeholders
{
namespace helper
{
template<typename... TList> struct arglist { };
typedef arglist<> nullargs;
template<unsigned int N, typename... TList> struct at;
template<unsigned int N, typename T, typename... TList>
struct at< N, arglist<T, TList...> >
{
typedef typename at< N - 1, arglist<TList...> >::type type;
};
template<typename T, typename... TList>
struct at< 0, arglist<T, TList...> >
{
typedef T type;
};
} // end of placeholders::helper
template<int n> struct Arg
{
template<typename ... TList>
struct apply
{
using type = typename helper::at<n - 1, helper::arglist<TList...> >::type;
};
private:
};
using _1 = Arg<1>;
using _2 = Arg<2>;
using _3 = Arg<3>;
using _4 = Arg<4>;
} // end of placeholders
}
}
#endif
復制代碼
如上,_1::apply<int, char, float>::type為int, _2::apply<int, char, float>::type為char。若不太清楚原理請參考:
http://www.cnblogs.com/flytrace/p/3551414.html
以上要注意的arglist是從0開始索引的,而外部Arg是從1開始索引的。
至此讓我們把push_back<_1, _2>完成:
復制代碼
template<>
struct push_back< _1, _2 >
{
template<typename... TList>
struct apply
{
using type = typename push_back<
typename _1::apply<TList...>::type,
typename _2::apply<TList...>::type>::type;
};
};
復制代碼
fold_s把固定的一堆參數傳入時,push_back總能挑選到正確位置的參數。下面我們來看看一個奇妙的改變,這將會讓你恍然大悟_1, _2占位符的設計和來歷。
讓我們把上面的代碼中所有_1,_2的地方全部調換位置,得到一個新的特化:
復制代碼
template<>
struct push_back< _2, _1 >
{
template<typename... TList>
struct apply
{
using type = typename push_back<
typename _2::apply<TList...>::type,
typename _1::apply<TList...>::type>::type;
};
};
復制代碼
使用這個新特化時,fold_s傳入的第二個參數將被放到push_back的第一個參數位置,而_2位於push_back第一個參數的樣子正好很形象的描述了這個行為。
現在你明白了吧,push_back<_1,_2>和push_back<_2,_1>這2個特化組合在一起,讓我們有了能夠指稱第一,第二個參數的能力。
這確實非常帥。很可惜當參數個數n增長時,你需要覆蓋n!種特化。參數為5時你將不得不寫120個特化。boost使用preprocessor來自動生成這些類,你仔細觀察上述類的結構,
確實都是可以自動生成的。我表示看了preprocessor幾眼就要瞎掉,有興致再研究。下面是我寫的更簡單的自動構造宏:
復制代碼
#ifndef HI_MPL_SUPPORT_LAMBDA_H_INCLUDE
#define HI_MPL_SUPPORT_LAMBDA_H_INCLUDE
#define STRINGLIZE_CAT_TOKEN(s1, s2, token) s1##token##s2
#define STRINGLIZE_CAT(s1, s2) s1##s2
#define ENUM_SHIFTED_PARAMS_MPL(n, prefix) \
ENUM_SHIFTED_PARAMS_MPL_##n(prefix)
#define ENUM_SHIFTED_PARAMS_MPL_1(prefix) prefix##1
#define ENUM_SHIFTED_PARAMS_MPL_2(prefix) prefix##1, prefix##2
#define ENUM_SHIFTED_PARAMS_MPL_3(prefix) prefix##1, prefix##2, prefix##3
#define ENUM_SHIFTED_PARAMS_MPL_4(prefix) prefix##1, prefix##2, prefix##3, prefix##4
#define ENUM_SHIFTED_PARAMS_MPL_5(prefix) prefix##1, prefix##2, prefix##3, prefix##4, prefix##5
#define SUPPORT_LAMBDA(classname, n, prefix) \
SUPPORT_LAMBDA_##n(classname, prefix)
#define SUPPORT_LAMBDA_1_IMPL(classname, A1) \
template<> \
struct classname##< A1 > \
{ \
template<typename... TList> \
struct apply \
{ \
using type = typename classname##< \
typename A1::apply<TList...>::type>::type; \
}; \
};
#define SUPPORT_LAMBDA_2_IMPL(classname, A1, A2) \
template<> \
struct classname##< A1, A2 > \
{ \
template<typename... TList> \
struct apply \
{ \
using type = typename classname##< \
typename A1::apply<TList...>::type, \
typename A2::apply<TList...>::type>::type; \
}; \
};
#define SUPPORT_LAMBDA_3_IMPL(classname, A1, A2, A3) \
template<> \
struct classname##< A1, A2, A3 > \
{ \
template<typename... TList> \
struct apply \
{ \
using type = typename classname##< \
typename A1::apply<TList...>::type, \
typename A2::apply<TList...>::type, \
typename A3::apply<TList...>::type>::type; \
}; \
};
#define SUPPORT_LAMBDA_4_IMPL(classname, A1, A2, A3, A4) \
template<> \
struct classname##< A1, A2, A3, A4 > \
{ \
template<typename... TList> \
struct apply \
{ \
using type = typename classname##< \
typename A1::apply<TList...>::type, \
typename A2::apply<TList...>::type, \
typename A3::apply<TList...>::type \
typename A4::apply<TList...>::type>::type; \
}; \
};
#define SUPPORT_LAMBDA_5_IMPL(classname, A1, A2, A3, A4, A5) \
template<> \
struct classname##< A1, A2, A3, A4, A5 > \
{ \
template<typename... TList> \
struct apply \
{ \
using type = typename classname##< \
typename A1::apply<TList...>::type, \
typename A2::apply<TList...>::type, \
typename A3::apply<TList...>::type \
typename A4::apply<TList...>::type \
typename A5::apply<TList...>::type>::type; \
}; \
};
#define SUPPORT_LAMBDA_1(classname, P) \
SUPPORT_LAMBDA_1_IMPL(classname, P##1)
#define SUPPORT_LAMBDA_2(classname, P) \
SUPPORT_LAMBDA_2_IMPL(classname, P##1, P##2) \
SUPPORT_LAMBDA_2_IMPL(classname, P##2, P##1)
#define SUPPORT_LAMBDA_3(classname, P) \
SUPPORT_LAMBDA_3_IMPL(classname, P##1, P##2, P##3) \
SUPPORT_LAMBDA_3_IMPL(classname, P##1, P##3, P##2) \
SUPPORT_LAMBDA_3_IMPL(classname, P##2, P##1, P##3) \
SUPPORT_LAMBDA_3_IMPL(classname, P##2, P##3, P##1) \
SUPPORT_LAMBDA_3_IMPL(classname, P##3, P##1, P##2) \
SUPPORT_LAMBDA_3_IMPL(classname, P##3, P##2, P##1)
#define SUPPORT_LAMBDA_4(classname, P) \
SUPPORT_LAMBDA_4_IMPL(classname, P##1, P##2, P##3, P##4) \
SUPPORT_LAMBDA_4_IMPL(classname, P##1, P##2, P##4, P##3) \
SUPPORT_LAMBDA_4_IMPL(classname, P##1, P##3, P##2, P##4) \
SUPPORT_LAMBDA_4_IMPL(classname, P##1, P##3, P##4, P##2) \
SUPPORT_LAMBDA_4_IMPL(classname, P##1, P##4, P##3, P##2) \
SUPPORT_LAMBDA_4_IMPL(classname, P##1, P##4, P##2, P##3) \
SUPPORT_LAMBDA_4_IMPL(classname, P##2, P##1, P##3, P##4) \
SUPPORT_LAMBDA_4_IMPL(classname, P##2, P##1, P##4, P##3) \
SUPPORT_LAMBDA_4_IMPL(classname, P##2, P##3, P##1, P##4) \
SUPPORT_LAMBDA_4_IMPL(classname, P##2, P##3, P##4, P##1) \
SUPPORT_LAMBDA_4_IMPL(classname, P##2, P##4, P##1, P##3) \
SUPPORT_LAMBDA_4_IMPL(classname, P##2, P##4, P##3, P##1) \
SUPPORT_LAMBDA_4_IMPL(classname, P##3, P##1, P##2, P##4) \
SUPPORT_LAMBDA_4_IMPL(classname, P##3, P##1, P##4, P##2) \
SUPPORT_LAMBDA_4_IMPL(classname, P##3, P##2, P##1, P##4) \
SUPPORT_LAMBDA_4_IMPL(classname, P##3, P##2, P##4, P##1) \
SUPPORT_LAMBDA_4_IMPL(classname, P##3, P##4, P##1, P##2) \
SUPPORT_LAMBDA_4_IMPL(classname, P##3, P##4, P##2, P##1) \
SUPPORT_LAMBDA_4_IMPL(classname, P##4, P##1, P##2, P##3) \
SUPPORT_LAMBDA_4_IMPL(classname, P##4, P##1, P##3, P##2) \
SUPPORT_LAMBDA_4_IMPL(classname, P##4, P##2, P##1, P##3) \
SUPPORT_LAMBDA_4_IMPL(classname, P##4, P##2, P##3, P##1) \
SUPPORT_LAMBDA_4_IMPL(classname, P##4, P##3, P##1, P##2) \
SUPPORT_LAMBDA_4_IMPL(classname, P##4, P##3, P##2, P##1)
#endif
復制代碼
在每個你希望支持占位符的類定義後邊,加上SUPPORT_LAMBDA這句宏,填入參數總數,占位符前綴(可包含命名空間,默認占位符必須以本身數字結束)。如下例子
復制代碼
template<typename T, typename... TList> struct push_back;
template<typename T, typename... TList>
struct push_back< typelist<TList...>, T>
{
typedef typelist<TList..., T> type;
};
template<>
struct push_back< nulllist >
{
typedef nulllist type;
};
SUPPORT_LAMBDA(push_back, 2, placeholders::_);
復制代碼
以上這一套實現占位符的辦法,比boost的要簡潔了很多。當然還缺少匿名占位符這樣的手法,這裡提供一個簡易的思路,望你有所得。