1. 模板参数包
(1)模板参数包的类型
①类型参数包(Type parameter packs):如template<typname… T> class tuple;
②非类型参数包(Non-type parameter packs):如template<int…A> class Test{}; Test<1,0,2> test;
③模板模板参数包(Template template parameter packs):见后面的例子
(2)使用注意事项
①在模板参数可被推导的情况下(如函数模板和类模板的偏特化),可在模板参数列表中声明多个模板参数包。
②但如果无法推导出模板参数,则模板参数列表中最多只能声明一个模板参数包,而且这个参数包必须是模板中的最后一个模板参数。
③默认参数不能用于模板参数包。如template<typename...T=int> struct foo1{};
(3)模板模板参数包
template<typename I, template<typename> class... B> struct Container{}; template<typename I, template<typename> class A, template<typename> class... B> struct Container<I, A, B...> { A<I> a; //A本身是一个模板 Container<I, B...> b; //B...为模板模板参数包,注意声明为template<typename> class... B> }; template<typename I> struct Container<I>{};
【编程实验】完美转发参数包
#include <iostream> using namespace std; struct A { A(){cout << "Constructed " << __func__ << endl;} A(const A& a){cout << "Copy Constructed " << __func__ << endl;} A(A&& a) {cout << "Move Constructed " << __func__ << endl;} }; struct B { B(){cout << "Constructed " << __func__ << endl;} B(const B& a){cout << "Copy Constructed " << __func__ << endl;} B(B&& a) {cout << "Move Constructed " << __func__ << endl;} }; //可变参数模板的定义 template<typename ...T> struct Tuple; template<typename T1, typename...T> struct Tuple<T1, T...> : public Tuple<T...> { typename std::decay<T1>::type t1; //T1可能带有cv和引用属性,去除它们 //注意,如果直接定义T1 t1,则可能t1是个引用 Tuple<T1, T...>(T1 a, T...b):t1(a), Tuple<T...>(b...) { cout << "Tuple<T1, T...>(T1 a, T... b): "; cout << "T1 is reference: " << std::is_reference<T1>::value<< endl; } }; template<> struct Tuple<> { Tuple(){cout << "Tuple<>" << endl;} }; //完美转发参数包 template<template<typename...> class Tp, typename...Args> Tp<Args...> Build(Args&&... args) { return Tp<Args...>(std::forward<Args>(args)...); } int main() { A a; B b; Build<Tuple>(a, b); return 0; } /*输出结果 e:StudyC++1122>g++ -std=c++11 test2.cpp -fno-elide-constructors e:StudyC++1122>a.exe Constructed A Constructed B Tuple<> Copy Constructed B Tuple<T1, T...>(T1 a, T... b): T1 is reference: 1 Copy Constructed A Tuple<T1, T...>(T1 a, T... b): T1 is reference: 1 Move Constructed B //以下两个移动构造的调用是由Build函数返回值引起的 Move Constructed A */
2. 函数参数包
(1)使用注意事项
①函数参数包分为两种:当参数包为最后一个模板参数,被称为trailing函数参数包,否则为non-trailing函数参数包。
②函数模板可以带trailing和non-trailing的函数参数包。
③non-trailing函数参数包只有在调用时显式指定参数类型时才能被推导出来。如果没有显式指定参数,则non-trailing参数包则会被推导为空。
【实例分析】参数包的推导
//1. 类型参数包 template<class... T> class X{}; X<> a; //空的参数列表 X<int> b; //一个参数 X<int, char, float> c; //3个参数 //2. 非类型参数包 template<bool...A> class X{}; //bool为非类型参数 X<> a; //空的参数列表 X<true> b; //一个参数 X<true, false, true> c; //3个参数 //3. 多个模板参数包 //3.1 参数包不能被推导 template<class...A, class...B> struct Container1{}; template<class...A, class B> struct Container2{}; template<class...A, class...B, class C> struct Container3{}; Container1<int, float, double> c1; //error,A和B无法被推导出来 Container2<int, float, double> c2; //error, 推导时会认为参数包A为int,float,double //但B却无指定类型,所以报错。要求将参数包放最后面。 Container3<int, float, double> c3; //error, A和B无法被推导 template<class A, class...B> struct Container4{}; Container4<int, float, double> c4; //A为int, B为float,double //3.2 多个参数包(可以被推导出来) template<typename T1, typename T2> struct foo{}; template<typename... T> struct bar{}; //泛化 template<typename... T1, typename... T2> //特化 struct bar<foo<T1, T2>...>{}; //ok template<typename A, typename B>struct S{}; template< template<typename...> class T, typename... TArgs, template<typename...> class U, typename... UArgs > struct S< T<TArgs...>, U<UArgs...> > {}; S<int, float> p; //匹配泛化模板 //其中的T和U推导是根据tuple的定义进行的,直至推导到边界条件(T和U两个 //tuple都不再包含模板参数),即template<typename A, typename B> S{}为止。 S<std::tuple<int, char>, std::tuple<float>> s; //3.3 多个参数包 struct a1 {}; struct a2 {}; struct a3 {}; struct a4 {}; struct a5 {}; template<class...X> struct baseC{}; template<> struct baseC<>{}; template<class...A1> struct container{container(){cout << "container<A1...>" <<endl;}}; //特化版:注意ver2,ver3,ver4将无法被匹配。 template<class...A, class...B, class...C> //ver 1:baseC中至少2个参数A,B struct container<baseC<A,B,C...>...>{container(){cout <<"baseC<A,B,C...>..." <<endl;}}; template<class...A, class...B, class...C> //ver 2:baseC中由于参数包“B...”会“吃掉”所有参数,所以C无法推导 //下面的声明尽管B...位于C的前面,但由于C本身为参数包类型,可能包含0个参数,所以这样的声明又符合语法规则。 struct container<baseC<A,B...,C>...>{container(){cout <<"baseC<A,B...,C>..." <<endl;}}; template<class...A, class...B, class...C> //ver 3:baseC中A参数包会“吃掉”所有参数,所以B、C无法推导 struct container<baseC<A...,B,C>...>{container(){cout <<"baseC<A...,B,C>..." <<endl;}}; template<class...A, class...B, class...C> //ver 4:baseC中A参数包会“吃掉”所有参数,所以B、C无法推导 struct container<baseC<A...,B,C...>...>{container(){cout <<"baseC<A...,B,C...>..." <<endl;}}; container<baseC<a1, a2, a5, a5, a5>, baseC<a1, a1, a5, a5, a5>, baseC<a1, a1, a5, a5, a5>, baseC<a1, a1, a5, a5, a5> > test1; //4. 参数包不能带默认值 template<typename...T=int> struct foo1{}; //error //1. 函数参数包 template<class...A> void func(A...args){}; func(); //空参数列表 func(1); //带1个参数 func(1,2,3,4,5); //带多个同类型的参数 func(1,'x',aWidget); //带多个不同类型的参数 //2. 多参数包的函数模板 template<class...A, class...B> void func(A...arg1,int sz1, int sz2, B...args) { assert(sizeof...(arg1) == sz1); assert(sizeof...(arg2) == sz2); } //arg1为non-trailing类型必须显式指定,得A:(int,int,int) B:(int,int,int,int,int) func<int,int,int>(1,2,3,3,5,1,2,3,4,5) //arg1为non-trailing类型,必须显式指定类型如<int,int,int> func(0,5,1,2,3,4,5);//A为non-trailing,但此处没有显式,所以为空,B(int,int,int,int,int)
3. 包扩展
(1)参数包可展开的位置
①表达式 ②初始化列表 ③基类描述列表 ④类成员初始化列表 ⑤模板参数列表 ⑥异常规格说明列表 ⑦lambda函数的捕获列表
【实例分析】参数包可展开的位置
//1. 出现在表达式中 template<class...A> void func1(A...args){ cout << sizeof...(args) << endl; } template<class...A> int func(A...args){ func1(99,99,args...,99,99); //args...出现在函数参数列表的表达式中 func1(args...,99); func1(99,args...); func1(args...); } //1. 包扩展出现在初始化列表中 template<class ...A> void func(A... args) { const int size = sizeof...(args+5); int res[size] = {99, 98, args...,97,96,95};//args...出现在初始化列表中 } //2. 包扩展出现基类描述列表和类成员初始化列表中 template<class ...A> struct Container : public Base<A>... //出现在基类描述列表中,相当于多重继承Base<A1>,Base<A2>,... { Container():Base<A>(12)...{}; //出现在类成员初始化列中表,等价于Base<A1>(12),Base<A2>(12),... }; //3. 包扩展出现在模板参数列表中 template<typename... I> struct container { container() { int array[sizeof...(I)] = {I()...};//出现在初始化列表中 } }; 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; //ok } //4. 包扩展出现在异常规格说明中 template<class ...X> void func(int arg) throw(X...) //出现在异常规格说明中,相当于throw(X1, X2,...) { } //5. 包扩展出现在lambda的捕获列表中 template<class ...Args> void f(Args... args) { auto lm = [&, args...] { return g(args...); }; lm(); }
(2)注意事项
①参数包必须通过包扩展表达式来扩展开来。单纯地使用未被扩展的包名是错误的。如:template<class…B> struct container<B>{}; //B未被扩展,应写成B…。注意container也应该是个可变参数模板。
②非参数包,不能用在包扩展表中。如
template<class A, class…B> void func1(container<A>…args){}; //A不是参数包,不能用在包扩展表达式中。
③如果在同一个包扩展表达式中引用了多个参数包,则他们的扩展是同步的,这些参数包必须拥有相同的长度。
//同一个包扩展表达式中,存在多个参数包 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; //Args1和Args2在同一个包扩展表达式中 }; }; //Pair<Arg1, Args2>...展开为Pair<short, unsigned short>, Pair<int, unsigned int> typedef zip<short, int> ::with<unsigned short, unsigned int>::type T1; //由于Arg1和Args2在同一个包扩展表达式(Pair<Arg1, Args2>...)中,所以展开后参数的数量 //应该一致。 //typedef zip<short>::with<unsigned short, unsigned int>::type T2; //error,Args1和Args2参数数量不同
④如果包扩展嵌套在另一个包扩展中,则出现在最内层包扩展内的参数包先被扩展。
//包扩展表达式的嵌套 template<class...Args> void f(Args...args){} template<class...Args> void g(Args...args) { f(const_cast<const Args*>(&args)...);//Args和args同步被展开,等价于 //f(const_cast<const Args1*>(&args1),const_cast<const Args2*>(&args2),...); //嵌套的包扩展: //内部包扩展args...先展开:得h(E1,E2,E3) + args... //再展开外部的args...,得h(E1,E2,E3)+E1, h(E1,E2,E3)+E2,h(E1,E2,E3)+E3 f(h(args...) + args...); }