形参包
模板形参包是接受零个或更多个模板实参(非类型、类型或模板)的模板形参。函数形参包是接受零个或更多个函数实参的函数形参。
至少有一个形参包的模板被称作变参模板。
变参类模板可以用任意数量的模板实参实例化:
1 2 3 4 5 6 7
| template<class... Types> struct Tuple {}; Tuple<> t0; Tuple<int> t1; Tuple<int, float> t2; Tuple<0> error;
|
变参函数模板可以用任意数量的函数实参调用(模板实参通过模板实参推导推导):
1 2 3 4 5 6
| template<class... Types> void f(Types... args); f(); f(1); f(2, 1.0);
|
在主类模板中,模板形参包必须是模板形参列表的最后一个形参。在函数模板中,模板参数包可以在列表中更早出现,只要其后的所有形参都可以从函数实参推导或拥有默认实参即可:
1 2 3 4 5 6 7 8 9 10
| template<typename U, typename... Ts> struct valid;
template<typename... Ts, typename U, typename=void> void valid(U, Ts...);
valid(1.0, 1, 2, 3);
|
如果变参模板的每个合法的特化都要求空模板形参包,那么程序非良构,不要求诊断。
包展开
后随省略号且其中至少有一个形参包的名字至少出现了一次的模式会被展开成零个或更多个逗号分隔的模式实例,其中形参包的名字按顺序被替换成包中的各个元素:
1 2 3 4 5 6 7 8 9 10 11 12 13
| template<class... Us> void f(Us... pargs) {} template<class... Ts> void g(Ts... args) { f(&args...); } g(1, 0.2, "a");
|
如果两个形参包在同一模式中出现,那么它们同时展开而且长度必须相同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| template<typename...> struct Tuple {}; template<typename T1, typename T2> struct Pair {}; template<class... Args1> struct zip { template<class... Args2> struct with { typedef Tuple<Pair<Args1, Args2>...> type; }; }; typedef zip<short, int>::with<unsigned short, unsigned>::type T1;
typedef zip<short>::with<unsigned short, unsigned>::type T2;
|
如果包展开内嵌于另一个包展开中,那么它所展开的是在最内层包展开出现的形参包,并且在外围(而非最内层)的包展开中必须提及其它形参包:
1 2 3 4 5 6 7 8 9 10 11
| template<class... Args> void g(Args... args) { f(const_cast<const Args*>(&args)...); f(h(args...) + args...); }
|
展开场所
展开所产生的逗号分隔列表按发生展开的各个场所可以是不同种类的列表:函数形参列表,成员初始化器列表,属性列表,等等。以下列出了所有允许的语境。
函数实参列表
包展开可以在函数调用运算符的括号内出现,此时省略号左侧的最大表达式或花括号初始化器列表是被展开的模式:
1 2 3 4 5 6 7 8 9
| f(&args...); f(n, ++args...); f(++args..., n); f(const_cast<const Args*>(&args)...);
f(h(args...) + args...);
|
正式而言,函数调用表达式中的表达式列表被归类为初始化器列表,它的模式是初始化器子句,它是赋值表达式和花括号初始化器列表其中之一。
有括号初始化器
包展开可以在直接初始化器,函数式转型及其他语境(成员初始化器,new 表达式等)的括号内出现,这种情况下的规则与适用于上述函数调用表达式的规则相同:
1 2 3 4
| Class c1(&args...); Class c2 = Class(n, ++args...); ::new((void *)p) U(std::forward<Args>(args)...)
|
花括号包围的初始化器
在花括号初始化器列表(花括号包围的初始化器和其他花括号初始化器列表的列表,用于列表初始化和其他一些语境中)中,也可以出现包展开:
1 2 3 4 5 6 7 8 9
| template<typename... Ts> void func(Ts... args) { const int size = sizeof...(args) + 2; int res[size] = {1, args..., 2}; int dummy[sizeof...(Ts)] = {(std::cout << args, 0)...}; }
|
模板实参列表
包展开可以在模板实参列表的任何位置使用,前提是模板拥有与该展开相匹配的形参:
1 2 3 4 5 6 7
| template<class A, class B, class... C> void func(A arg1, B arg2, C...arg3) { container<A, B, C...> t1; container<C..., A, B> t2; container<A, C..., B> t3; }
|
函数形参列表
在函数形参列表中,如果省略号在某个形参声明中(无论它是否指名函数形参包(例如在 Args … args中)出现,那么该形参声明是模式:
1 2 3 4 5 6 7 8 9
| template<typename... Ts> void f(Ts...) {} f('a', 1); f(0.1); template<typename... Ts, int... N> void g(Ts (&...arr)[N]) {} int n[1]; g<const char, int>("a", n);
|
注意:在模式 Ts (&...arr)[N]
中,省略号是最内层的元素,而不是像所有其他包展开中一样是最后的元素。
注意:不能用 Ts (&...)[N]
,因为 C++11 语法要求带括号的省略号形参拥有名字:CWG 问题 1488。
模板形参列表
包展开可以在模板形参列表中出现:
1 2 3 4 5 6
| template<typename... T> struct value_holder { template<T... Values> struct apply {}; };
|
基类说明符与成员初始化器列表
包展开可以用于指定类声明中的基类列表。通常这也意味着它的构造函数也需要在成员初始化器列表中使用包展开,以调用这些基类的构造函数:
1 2 3 4 5 6
| template<class... Mixins> class X : public Mixins... { public: X(const Mixins&... mixins) : Mixins(mixins)... {} };
|
Lambda 捕获
包展开可以在 lambda 表达式的捕获子句中出现:
1 2 3 4 5 6
| template<class... Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); }
|
sizeof… 运算符
sizeof… 也被归类为包展开:
1 2 3 4 5
| template<class... Types> struct count { static const std::size_t value = sizeof...(Types); };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #include <iostream> void tprintf(const char* format) { std::cout << format; } template<typename T, typename... Targs> void tprintf(const char* format, T value, Targs... Fargs) { for (; *format != '\0'; format++) { if ( *format == '%' ) { std::cout << value; tprintf(format + 1, Fargs...); return; } std::cout << *format; } } int main() { tprintf("% world% %\n", "Hello", '!', 123); return 0; }
|