我曾在公司内部的一次技术培训课程中讲到如何展开可变模板参数的问题,具体来说是如何打印可变模板参数,我初略数了一下,有很多种,下面来看看到底有多少种不同的方法展开可变模板参数吧。
//展开变参的N种方法, 以print为例 //----写法1 template<typename T> void print(T t) { cout << t << endl; } template<typename T, typename... Args> void print(T t, Args... args) { print(t); print(args...); }
写法1很普通,没什么特别的,算是中规中矩,也是一般情况下的展开方式,下面来看特殊一点的展开方式:
//----写法2 template<typename T> void printarg(T t) { cout << t << endl; } template<typename... Args> void print2(Args... args) { //int a[] = { (printarg(args), 0)... }; std::initializer_list<int>{(printarg(args), 0)...}; }
写法2比较巧妙,借助初始化列表和逗号表达式来展开,在生成初始化列表的过程中展开参数包。这种方式不够间接,我希望能把printarg函数干掉,于是第三种方法出来了:
//----写法3 template<typename... Args> void print3(Args... args) { std::initializer_list<int>{([&]{cout << args << endl; }(), 0)...}; }
template<typename... Args>
void expand(Args... args)
{
std::initializer_list<int>{(cout << args << endl, 0)...};
}
写法3通过lambda表达式简化书写,非常间接直观,是我最喜欢的方式。那么除了这三种展开方式之外还有第4种吗?答案是肯定的。来看第4种写法:
//----写法4 template<std::size_t I = 0, typename Tuple> typename std::enable_if<I == std::tuple_size<Tuple>::value>::type printtp(Tuple t) { //cout << "at range" << endl; } template<std::size_t I = 0, typename Tuple> typename std::enable_if<I < std::tuple_size<Tuple>::value>::type printtp(Tuple t) { std::cout << std::get<I>(t) << std::endl; printtp<I + 1>(t); } template<typename... Args> void print4(Args... args) { printtp(std::make_tuple(args...)); }
第4种写法也很巧妙,借助tuple和enable_if,通过不断地递增I来获取tuple中的元素并打印。至此已经完成4种写法了,还有没有第5种方法呢?答案还是肯定的。
//写法5 template <int N> class printer { public: template<typename Tuple> static void print(const Tuple& t) { //cout << std::get<N - 1>(t) << endl; //降序 printer<N-1>::print(t); cout << std::get<N - 1>(t) << endl; //升序 } }; template <> class printer<0> { public: template<typename Tuple> static void print(const Tuple& t) { } }; template<typename... Args> void print5(const Args&... args) { auto tp = std::make_tuple(args...); printer<sizeof...(args)>::print(tp); }
写法5的实现思路和写法4有异曲同工之妙,都是借助于int模板参数的递增来实现递归,不同的是写法是通过类模版的特化来实现递归的。已经5种了,还有第6种吗?看,第6种写法来了:
//写法6 template<int...> struct IndexTuple{}; template<int N, int... Indexes> struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...>{}; template<int... indexes> struct MakeIndexes<0, indexes...> { typedef IndexTuple<indexes...> type; }; template<int ... Indexes, typename ... Args> void print_helper(IndexTuple<Indexes...>, std::tuple<Args...>&& tup) { std::initializer_list<int>{([&]{cout << std::get<Indexes>(tup) << endl; }(), 0)...}; } template<typename ... Args> void print6(Args... args) { print_helper(typename MakeIndexes<sizeof... (Args)>::type(), std::make_tuple(args...)); }
第6种写法略显复杂,但很有意义,它实际上是进行了一个“氧化还原”反应,先将参数包转换为tuple,接着又将tuple转换为参数包,很有趣也很有用。至此,我们已经发现了6种展开可变模板参数的写法了,太棒了,你也许还兴犹未尽,还有第7种写法吗,有,但我已懒得再写了,其实还不止7种呢,那更多的写法在哪儿呢?在那儿,就在那儿等着你去发现……
/*****************更新***************/
将变参在展开过程中变为tuple<pair<...>>
#define MAKE_PAIR(text) std::pair<std::string, decltype(text)>{#text, text} template<typename T, typename T1, typename... Args> constexpr static inline auto apply(T const & t, const T1& first, const Args&... args) { return apply(std::tuple_cat(t, std::make_tuple(MAKE_PAIR(first))), args...); } template<typename T> constexpr static inline auto apply(T const & args) { return args; } #define META(...) auto meta(){ return apply(std::tuple<>(), __VA_ARGS__); }