Funny C++11 Template Expansion




C++11标准中增强了模板功能,新特性可变模板参数允许模板定义中包含0到任意个模板参数,“…”省略号表示任意个参数。示例:

1template<typename... Args>
2void printVars(Args... args) {
3	((cout << args << ' '),...);
4}
5
6printVars(1, 2, 3, 4, 5);  // 1 2 3 4 5

template<typename… Args> 表示template接受任意个参数。

((cout << args << ' '),...); 是将参数展开,如果args的个数是N个,则会被以此展开为((cout<<args[0]<<’ ’),(cout<<args[1]<<’ ’),(cout<<args[2]<<’ ’), ……, (cout<<args[N-1]<<’ ’)); 效果则是初始化了一个tuple,每项都会将对应的args打印一次。

上面是一个基本用法,还有更有趣的场景。如:

1template<class T, class... Args>
2T* create(SomeCastableType* args, size_t numArgs)
3{
4	// Question: How to get INDEX_OF_EXPANSION?
5  return new T(static_cast<Args>(args[INDEX_OF_EXPANSION])...);  
6}
7
8class Obj {
9	public A(int a, int b, int c)
10};
11int args[] = {1,2,3};
12Obj* obj = create<Obj, int, int, int>(args, 3);
13// What we need now is 
14// new T(static_cast<int>(args[0]), 
15// 			static_cast<int>(args[1]), 
16//      static_cast<int>(args[2]));

create接受一个SomeCastableType的参数列表和参数列表长度,并使用这个参数列表,创建一个T。

假如 SomeCastableType 可以转化为任意类型,唯一的问题就是需要某种方式能推导出列表args的下标(INDEX_OF_EXPANSION)。

答案当然是: 可以通过template来进行推导。

1template <size_t... Is>
2struct IndexSequence {};
3
4template <size_t N, size_t... Is>
5struct BuildIndexSequence : BuildIndexSequence<N - 1, N - 1, Is...> {};
6
7template <size_t... Is>
8struct BuildIndexSequence<0, Is...> : IndexSequence<Is...> {};
9
10// Let's adapt our create function
11template<class T, class... Args, std::size_t... Is>
12T* create(SomeCastableType* args, IndexSequence<Is...>)
13{
14  return new T(static_cast<Args>(args[Is])...);  
15}
16
17template<class T, class... Args>
18T* create(SomeCastableType* args, size_t numArgs)
19{
20  return create<T, Args...>(args, BuildIndexSequence<numArgs>);  
21}
22
23class Obj {
24	public A(int a, int b, int c)
25};
26int args[] = {1,2,3};
27// Now Pefect.
28Obj* obj = create<Obj, int, int, int>(args, 3);

上面重点是 通过 BuildIndexSequence<3> 创建了一个 IndexSequence<0,1,2> 序列。T(static_cast<Args>(args[Is])...); 就被展开成了 T(static_cast<int>(args[0]), static_cast<int>(args[1]), static_cast<int>(args[2]));

相信第一次看到这种代码的同学会相当诧异,BuildIndexSequence 这是什么写法?

翻阅相关资料(《深入应用C++11》)后,这种方式叫 继承方式展开参数包,而且书里有个几乎一模一样的例子。模板子类会不停的展开父类,直到遇到特化的终止条件展开过程才结束,而且这一切推导都会在编译期完成,不会占用运行时间。

BuildIndexSequence<3>的展开过程如下:

1// template <size_t N, size_t... Is>
2// struct BuildIndexSequence : BuildIndexSequence<N - 1, N - 1, Is...> {};
3struct BuildIndexSequence<3> : BuildIndexSequence<2, 2> {};
4BuildIndexSequence<2, 2> : BuildIndexSequence<1, 1, 2> {};
5BuildIndexSequence<1, 1, 2> : BuildIndexSequence<0, 0, 1, 2> {};
6
7//template <size_t... Is>
8//struct BuildIndexSequence<0, Is...> : IndexSequence<Is...> {};
9BuildIndexSequence<0, 0, 1, 2> : IndexSequence<0, 1, 2>

而在C++14里,直接提供了一个 std::make_index_sequence 实现该功能。

上述用法可以一窥可变模板参数的功能,事实上C++11的可变模板参数,再配合C++11的type_traits (type_traits提供了丰富的编译期计算、查询、判断、转换和选择的帮助类), 可以在编译期实现大量花里胡哨的功能, 如实现动态语言都有的,但C++本没有的 反射 功能。

这里有一个Template的库可以作为参考和学习:

GitHub - Ubpa/UTemplate


2023-12-27


© 2024 Jay. All Rights Reserved.