Before C++11, 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 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 are 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<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 items
In
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 items
In
this example, the non-type parameter pack A is expanded
into a list of zero or more non-type template parameters.// error
template<class...A, class...B>struct container1{};
// error
template<class...A,class B>struct container2{};
In this example, the compiler issues two error messages.
One error message is for class template container1 because container1 has
two template parameter packs A and B that
cannot be deduced. The other error message is for class template container2 because
template parameter pack A is not the last template
parameter of container2, and A cannot
be deduced.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.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);
#include <cassert>
template<class...A, class...B> void func(A...arg1,int sz1, int sz2, B...arg2)
{
assert( sizeof...(arg1) == sz1);
assert( sizeof...(arg2) == sz2);
}
int main(void)
{
//A:(int, int, int), B:(int, int, int, int, int)
func<int,int,int>(1,2,3,3,5,1,2,3,4,5);
//A: empty, B:(int, int, int, int, int)
func(0,5,1,2,3,4,5);
return 0;
}
In this example, function template func has
two function parameter packs arg1 and arg2. arg1 is
a non-trailing function parameter pack, and arg2 is
a trailing function parameter pack. When func is
called with three explicitly specified arguments as func<int,int,int>(1,2,3,3,5,1,2,3,4,5),
both arg1 and arg2 are deduced successfully.
When func is called without explicitly specified
arguments as func(0,5,1,2,3,4,5), arg2 is
deduced successfully and arg1 is empty. In this example,
the template parameter packs of function template func can
be deduced, so func can have more than one template
parameter pack.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.Expression list
#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.Initializer list
#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 95
In
this example, the pack expansion args... is in the
initializer list of the array res.Base specifier list
#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 ctor
In 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.Member initializer list
#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 ctor
In 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).Template argument list
#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.Exception specification list
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 10
In 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.