简介
该篇博客主要介绍C++11中的变长模板,对变长模板的原理和使用方法进行介绍。本篇博客参考书籍深入理解C++11新特性解析与应用一书,非常推荐该书作为C++11学习的参考资料,英语好的话更推荐直接阅读C++官网中的关于C++11新特性的介绍。
该本书我已经上传高清pdf版本,并且包含非常详细的书签,下载地址如下(为上传修改了名称,放心下载):
C++11新特性解析与应用
模板和函数参数包
模板参数包说明
变长类模板的形式如下:
template <typename... Elements> class tuple;
标识符Elements
之前的使用了省略号来表示该参数是变长的。在C++11中,Elements
就被称作是一个模板参数包(template parameter pack)
,这是一种新的模板参数类型。它可以接受任意多个参数作为模板参数,实例化的tuple
模板类如下所示:
tuple<int, char, double>
与普通的模板参数类似,模板参数包也可以是非类型的,例如
template<int... A> class NonTypeVT { };
NonTypeVT<1,0,2> vtvt;
除了类型的模板参数包和非类型的模板参数包,模板参数包实际上还是模板类型的,后面讨论。
解包
为了使用模板参数包,我们需要将其解包(unpack)。在C++11中,通过**包拓展(pack expansion)**的表达式来完成。例如:
template<typename... A> class Template: private B<A...> { };
这里的表达式A...
就是一个包拓展。参数包会在包拓展的位置展开为多个参数。比如:
template<typename T1, typename T2> class B {};
template<typename... A> class Template: private B<A...> { };
Template<X, Y> xy;
上面的列子中展示了包拓展的实现,但是该例子基于类模板B总是接受两个参数的前提下。若我们在这里声明了一个Template<X, Y, Z>
,就必然会发生模板推导的错误。为了解决上述问题,见后面内容中提出的解决办法。
在可变参数模板中使用递归
通过定义递归的模板偏特化定义,我们可以使得参数包在实例化时能够层层展开,直到参数包中的参数逐渐耗尽或到达某个数量的边界为止。
类模板使用递归代码如下所示(模板参数包):
template<typename... Elements> class tuple; // 变长模板的声明
template<typename Head, typename... Tail> // 递归的偏特化定义
class tuple<Head, Tail...> : private tuple<Tail...> {
Head head;
}
template<> class tuple<> {}; // 边界条件
上述代码中偏特化版本的tuple
包含了两个参数,一个是类型模板参数Head
,另一个则是模板参数包Tail
。tuple<double, int, char, float>
会引起基类的递归构造,这样的递归构造在tuple
的参数包为0个的时候会结束。这是由于我们定义了边界条件,即编译器从tuple<>
建造出tuple<float>
。
函数模板中使用地推代码如下所示(函数参数包):
// definition for 0 parameters -- terminating call
void show_list3() {}
// definition for 1 or more parameters
template<typename T, typename... Args>
void show_list3( T value, Args... args)
{
std::cout << value << ", ";
show_list3(args...);
}
模板参数包与函数参数包的区别是在C++11中,标准要求函数参数包必须唯一,且是函数的最后一个参数,而模板参数包则没有这样的要求。
进阶
C++11标准定义了以下7种参数包可以展开的位置:
- 表达式
- 初始化列表
- 基类描述列表
- 类成员初始化列表
- 模板参数列表
- 通用属性列表
- lambda函数的捕捉列表
不同的包拓展方式
template<typename... A> class T: private B<A>... { }; // (1)
template<typename... A> class T: private B<A...> { }; // (2)
上述两种在基类描述列表中解包后是不同的,对于同样的实例化T<X, Y>
,解包后的形式如下:
class T<X, Y>: private B<X>, private<Y> { }; // (1)
class T<X, Y>: private B<X, Y> { }; // (2)
类似的状况也会发生在函数模板中。
template<class... A> int Vaargs(A... args) {
int size = sizeof...(A);
}
在C++11中引入了新操作符sizeof...
,它的作用是计算参数包中的参数个数。