Before C++0x, templates had a fixed number of parameters that must be specified in the declaration of the templates. Templates could not directly express a class or function template that had a variable number of parameters. To partially alleviate this problem in the existing C++ programs, you could use overloaded function templates that had a different number of parameters or extra defaulted template parameters.
With the variadic templates feature, you can define class or function templates that have any number (including zero) of parameters. To achieve this goal, this feature introduces a kind of parameter called a parameter pack to represent a list of zero or more parameters for templates.
The variadic template feature also introduces pack expansion to indicate that a parameter pack is expanded.
Two existing techniques, template argument deduction and partial specialization, can also apply to templates that have parameter packs in their parameter lists.
A parameter pack can be a type of parameter for templates. Unlike previous parameters, which can only bind to a single argument, a parameter pack can pack multiple parameters into a single parameter by placing an ellipsis to the left of the parameter name.
In the template definition, a parameter pack is treated as a single parameter. In the template instantiation, a parameter pack is expanded and the correct number of the parameters is created.
According to the context where a parameter pack is used, the parameter pack can be either a template parameter pack or a function parameter pack.
Template parameter packs
template<class...A> struct container{}; template<class...B> void func();In this example, A and B are template parameter packs.
template<class...T> class X{}; X<> a; // the parameter list is empty X<int> b; // the parameter list has one item X<int, char, float> c; // the parameter list has three itemsIn this example, the type parameter pack T is expanded into a list of zero or more type template parameters.
The following example shows a non-type parameter pack:
template<bool...A> class X{}; X<> a; // the parameter list is empty X<true> b; // the parameter list has one item X<true, false, true> c; // the parameter list has three itemsIn this example, the non-type parameter pack A is expanded into a list of zero or more non-type template parameters.
template<class...A, class...B>struct container{};In this example, the compiler issues an error message because the class template container has two template parameter packs A and B.
template<class...A,class B> struct container{};In this example, the compiler issues an error message because the template parameter pack A is not the last template parameter in the template parameter list of the class template container.
template<typename...T=int> struct foo1{};In this example, the compiler issues an error message because the template parameter pack T is given a default argument int.
Function parameter packs
A function parameter pack is a function parameter that represents zero or more function parameters. Syntactically, a function parameter pack is a function parameter specified with an ellipsis.
template<class...A> void func(A...args)In this example, A is a template parameter pack, and args is a function parameter pack. You can call the function with any number (including zero) of arguments:
func(); // void func(); func(1); // void func(int); func(1,2,3,4,5); // void func(int,int,int,int,int); func(1,'x', aWidget); // void func(int,char,widget);A function template can have at most one function parameter pack in its parameter list, and the function parameter pack must be the last function parameter, as shown in the following example:
// Okay. Function parameter pack arg2 is in the last position template<class...A, class B> void func(B arg1, A...arg2); // Error. Function parameter pack arg1 is not in the last position template<class...A, class B> void func(A...arg1,B arg2);In this example, the template arguments can be deduced in the function template func, so the template parameter pack A does not need to be the last template parameter. For the same reason, a function template in this context can have more than one template parameter pack in its parameter list. Consider the following example:
template<class...A> struct container{}; template<class...B, class...C> void func(container<B,C>...args);In this example, the function template func has two template parameter packs B and C in its parameter list.
template<class...T> void func(T...a){}; template<class...U> void func1(U...b){ func(b...); }In this example, T... and U... are the corresponding pack expansions of the template parameter packs T and U, and b... is the pack expansion of the function parameter pack b.
#include <cstdio> #include <cassert> template<class...A> void func1(A...arg){ assert(false); } void func1(int a1, int a2, int a3, int a4, int a5, int a6){ printf("call with(%d,%d,%d,%d,%d,%d)\n",a1,a2,a3,a4,a5,a6); } template<class...A> int func(A...args){ int size = sizeof...(A); switch(size){ case 0: func1(99,99,99,99,99,99); break; case 1: func1(99,99,args...,99,99,99); break; case 2: func1(99,99,args...,99,99); break; case 3: func1(args...,99,99,99); break; case 4: func1(99,args...,99); break; case 5: func1(99,args...); break; case 6: func1(args...); break; default: func1(0,0,0,0,0,0); } return size; } int main(void){ func(); func(1); func(1,2); func(1,2,3); func(1,2,3,4); func(1,2,3,4,5); func(1,2,3,4,5,6); func(1,2,3,4,5,6,7); return 0; }The output of this example:
call with (99,99,99,99,99,99) call with (99,99,1,99,99,99) call with (99,99,1,2,99,99) call with (1,2,3,99,99,99) call with (99,1,2,3,4,99) call with (99,1,2,3,4,5) call with (1,2,3,4,5,6) call with (0,0,0,0,0,0)In this example, the switch statement shows the different positions of the pack expansion args... within the expression lists of the function func1. The output shows each call of the function func1 to indicate the expansion.
#include <iostream> using namespace std; void printarray(int arg[], int length){ for(int n=0; n<length; n++){ printf("%d ",arg[n]); } printf("\n"); } template<class...A> void func(A...args){ const int size = sizeof...(args) +5; printf("size %d\n", size); int res[sizeof...(args)+5]={99,98,args...,97,96,95}; printarray(res,size); } int main(void) { func(); func(1); func(1,2); func(1,2,3); func(1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20); return 0; }The output of this example:
size 5 99 98 97 96 95 size 6 99 98 1 97 96 95 size 7 99 98 1 2 97 96 95 size 8 99 98 1 2 3 97 96 95 size 25 99 98 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 97 96 95In this example, the pack expansion args... is in the initializer list of the array res.
#include <iostream> using namespace std; struct a1{}; struct a2{}; struct a3{}; struct a4{}; template<class X> struct baseC{ baseC() {printf("baseC primary ctor\n");} }; template<> struct baseC<a1>{ baseC() {printf("baseC a1 ctor\n");} }; template<> struct baseC<a2>{ baseC() {printf("baseC a2 ctor\n");} }; template<> struct baseC<a3>{ baseC() {printf("baseC a3 ctor\n");} }; template<> struct baseC<a4>{ baseC() {printf("baseC a4 ctor\n");} }; template<class...A> struct container : public baseC<A>...{ container(){ printf("container ctor\n"); } }; int main(void){ container<a1,a2,a3,a4> test; return 0; }The output of this example:
baseC a1 ctor baseC a2 ctor baseC a3 ctor baseC a4 ctor container ctorIn this example, the pack expansion baseC<A>... is in the base specifier list of the class template container. The pack expansion is expanded into four base classes baseC<a1>, baseC<a2>, baseC<a3>, and baseC<a4>. The output shows that all the four base class templates are initialized before the instantiation of the class template container.
#include <iostream> using namespace std; struct a1{}; struct a2{}; struct a3{}; struct a4{}; template<class X> struct baseC{ baseC(int a) {printf("baseC primary ctor: %d\n", a);} }; template<> struct baseC<a1>{ baseC(int a) {printf("baseC a1 ctor: %d\n", a);} }; template<> struct baseC<a2>{ baseC(int a) {printf("baseC a2 ctor: %d\n", a);} }; template<> struct baseC<a3>{ baseC(int a) {printf("baseC a3 ctor: %d\n", a);} }; template<> struct baseC<a4>{ baseC(int a) {printf("baseC a4 ctor: %d\n", a);} }; template<class...A> struct container : public baseC<A>...{ container(): baseC<A>(12)...{ printf("container ctor\n"); } }; int main(void){ container<a1,a2,a3,a4> test; return 0; }The output of this example:
baseC a1 ctor:12 baseC a2 ctor:12 baseC a3 ctor:12 baseC a4 ctor:12 container ctorIn this example, the pack expansion baseC<A>(12)... is in the member initializer list of the class template container. The constructor initializer list is expanded to include the call for each base class baseC<a1>(12), baseC<a2>(12), baseC<a3>(12), and baseC<a4>(12).
#include <iostream> using namespace std; template<int val> struct value{ operator int(){return val;} }; template <typename...I> struct container{ container(){ int array[sizeof...(I)]={I()...}; printf("container<"); for(int count = 0; count<sizeof...(I); count++){ if(count>0){ printf(","); } printf("%d", array[count]); } printf(">\n"); } }; template<class A, class B, class...C> void func(A arg1, B arg2, C...arg3){ container<A,B,C...> t1; // container<99,98,3,4,5,6> container<C...,A,B> t2; // container<3,4,5,6,99,98> container<A,C...,B> t3; // container<99,3,4,5,6,98> } int main(void){ value<99> v99; value<98> v98; value<3> v3; value<4> v4; value<5> v5; value<6> v6; func(v99,v98,v3,v4,v5,v6); return 0; }The output of this example:
container<99,98,3,4,5,6> container<3,4,5,6,99,98> container<99,3,4,5,6,98>In this example, the pack expansion C... is expanded in the context of template argument list for the class template container.
struct a1{}; struct a2{}; struct a3{}; struct a4{}; struct a5{}; struct stuff{}; template<class...X> void func(int arg) throw(X...){ a1 t1; a2 t2; a3 t3; a4 t4; a5 t5; stuff st; switch(arg){ case 1: throw t1; break; case 2: throw t2; break; case 3: throw t3; break; case 4: throw t4; break; case 5: throw t5; break; default: throw st; break; } } int main(void){ try{ // if the throw specification is correctly expanded, none of // these calls should trigger an exception that is not expected func<a1,a2,a3,a4,a5,stuff>(1); func<a1,a2,a3,a4,a5,stuff>(2); func<a1,a2,a3,a4,a5,stuff>(3); func<a1,a2,a3,a4,a5,stuff>(4); func<a1,a2,a3,a4,a5,stuff>(5); func<a1,a2,a3,a4,a5,stuff>(99); } catch(...){ return 0; } return 1; }In this example, the pack expansion X... is expanded in the context of exception specification list for the function template func.
template<class...A> struct container; template<class...B> struct container<B>{}In this example, the compiler issues an error message because the template parameter pack B is not expanded.
template<class X> struct container{}; template<class A, class...B> // Error, parameter A is not a parameter pack void func1(container<A>...args){}; template<class A, class...B> // Error, 1 is not a parameter pack void func2(1...){};
struct a1{}; struct a2{}; struct a3{}; struct a4{}; struct a5{}; template<class...X> struct baseC{}; template<class...A1> struct container{}; template<class...A, class...B, class...C> struct container<baseC<A,B,C...>...>:public baseC<A,B...,C>{}; int main(void){ container<baseC<a1,a4,a5,a5,a5>, baseC<a2,a3,a5,a5,a5>, baseC<a3,a2,a5,a5,a5>,baseC<a4,a1,a5,a5,a5> > test; return 0; }In this example, the template parameter packs A, B, and C are referenced in the same pack expansion baseC<A,B,C...>.... The compiler issues an error message to indicate that the lengths of these three template parameter packs are mismatched when expanding them during the template instantiation of the class template container.
// primary template template<class...A> struct container; // partial specialization template<class B, class...C> struct container<B,C...>{};When the class template container is instantiated with a list of arguments, the partial specialization is matched in all cases where there are one or more arguments. In that case, the template parameter B holds the first parameter, and the pack expansion C... contains the rest of the argument list. In the case of an empty list, the partial specialization is not matched, so the instantiation matches the primary template.
template<class...A> struct container; // partial specialization template<class B, class...C> struct container<C...,B>{};In this example, the compiler issues an error message because the pack expansion C... is not the last argument in the argument list for the partial specialization.
template<typename T1, typename T2> struct foo{}; template<typename...T> struct bar{}; // partial specialization template<typename...T1,typename...T2> struct bar<foo<T1,T2>...>{};In this example, the partial specialization has two template parameter packs T1 and T2 in its parameter list.
#include<iostream> using namespace std; struct a1{}; struct a2{}; struct a3{}; struct a4{}; struct a5{}; struct a6{}; struct a7{}; struct a8{}; struct a9{}; struct a10{}; template<typename X1, typename X2> struct foo{foo();}; template<typename X3, typename X4> foo<X3,X4>::foo(){cout<<"primary foo"<<endl;}; template<> struct foo<a1,a2>{foo(){cout<<"ctor foo<a1,a2>"<<endl;}}; template<> struct foo<a3,a4>{foo(){cout<<"ctor foo<a3,a4>"<<endl;}}; template<> struct foo<a5,a6>{foo(){cout<<"ctor foo<a5,a6>"<<endl;}}; template<> struct foo<a7,a8>{foo(){cout<<"ctor foo<a7,a8>"<<endl;}}; template<> struct foo<a9,a10>{foo(){cout<<"ctor foo<a9,a10>"<<endl;}}; template<typename...T>struct bar{bar{}{cout<<"bar primary"<<endl;}}; template<typename A, typename B, typename...T1, typename...T2> struct bar<foo<A,B>,foo<T1,T2>...>{ foo<A,B> data; bar<foo<T1,T2>...>data1; }; template<> struct bar<foo<a9,a10> > {bar(){cout<<"ctor bar<foo<a9,a10>>"<<endl;}}; int main(){ bar<foo<a1,a2>,foo<a3,a4>,foo<a5,a6>,foo<a7,a8>,foo<a9,a10> > t2; return 0; }The output of the example:
ctor foo<a1,a2> ctor foo<a3,a4> ctor foo<a5,a6> ctor foo<a7,a8> ctor bar<foo<a9,a10>
template<class...A> void func(A...args){} int main(void){ func(1,2,3,4,5,6); return 0; }In this example, the function argument list is (1,2,3,4,5,6). Each function argument is deduced to the type int, so the template parameter pack A is deduced to the following list of types: (int,int,int,int,int,int). With all the expansions, the function template func is instantiated as void func(int,int,int,int,int,int), which is the template function with the expanded function parameter pack.
template<class...A> void func(A...args){} int main(void){ func(); return 0; }
#include <cstdio> template<int...A> struct container{ void display(){printf("YIKES\n");} }; template<int B, int...C> struct container<B,C...>{ void display(){ printf("spec %d\n",B); container<C...>test; test.display(); } }; template<int C> struct container<C>{ void display(){printf("spec %d\n",C);} }; int main(void) { printf("start\n\n"); container<1,2,3,4,5,6,7,8,9,10> test; test.display(); return 0; }The output of this example:
start Spec 1 Spec 2 Spec 3 Spec 4 Spec 5 Spec 6 Spec 7 Spec 8 Spec 9 spec 10In this example, the partial specialization of the class template container is template<int B, int...C> struct container<B,C...>. The partial specialization is matched when the class template is instantiated to container<1,2,3,4,5,6,7,8,9,10>. Template argument deduction deduces the template parameter pack C and the parameter B from the argument list of the partial specialization. Template argument deduction then deduces the parameter B to be 1, the pack expansion C... to a list: (2,3,4,5,6,7,8,9,10), and the template parameter pack C to the following list of types: (int,int,int,int,int,int,int,int,int).
If you change the statement container<1,2,3,4,5,6,7,8,9,10> test to container<1> test, template argument deduction deduces that the template parameter pack C is empty.
#include <cassert> template<class...A> int func(A...arg){ return sizeof...(arg); } int main(void){ assert(func<int>(1,2,3,4,5) == 5); return 0; }In this example, the template parameter pack A is deduced to a list of types: (int,int,int,int,int) using the explicit argument list and the arguments in the function call.