Variadic templates (C++11)

Note: IBM supports selected features of C++11, known as C++0x before its ratification. IBM will continue to develop and implement the features of this standard. The implementation of the language level is based on IBM's interpretation of the standard. Until IBM's implementation of all the C++11 features is complete, including the support of a new C++11 standard library, the implementation may change from release to release. IBM makes no attempt to maintain compatibility, in source, binary, or listings and other compiler interfaces, with earlier releases of IBM's implementation of the new C++11 features.

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.

Parameter packs

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 parameter packs
A template parameter pack is a template parameter that represents any number (including zero) of template parameters. Syntactically, a template parameter pack is a template parameter specified with an ellipsis. Consider the following example.
template<class...A> struct container{}; 
template<class...B> void func();
In this example, A and B are template parameter packs.
According to the type of the parameters contained in a template parameter pack, there are three kinds of template parameter packs:
  • Type parameter packs
  • Non-type parameter packs
  • Template template parameter packs
A type parameter pack represents zero or more type template parameters. Similarly, a non-type parameter pack represents zero or more non-type template parameters.
Note: Template template parameter packs are not supported in z/OS® XL C/C++ V2R1.
The following example shows a type parameter pack:
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.
In a context where template arguments can be deduced; for example, function templates and class template partial specializations, a template parameter pack does not need to be the last template parameter of a template. In this case, you can declare more than one template parameter pack in the template parameter list. However, if template arguments cannot be deduced, you can declare at most one template parameter pack in the template parameter list, and the template parameter pack must be the last template parameter. Consider the following example:
// 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.
Default arguments cannot be used for a template parameter pack. Consider the following example:
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.

In the definition of a function template, a function parameter pack uses a template parameter pack in the function parameters. The template parameter pack is expanded by the function parameter pack. Consider the following example:
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 parameter pack is a trailing function parameter pack if it is the last function parameter of a function template. Otherwise, it is a non-trailing function parameter pack. A function template can have trailing and non-trailing function parameter packs. A non-trailing function parameter pack can be deduced only from the explicitly specified arguments when the function template is called. If the function template is called without explicit arguments, the non-trailing function parameter pack must be empty, as shown in the following example:
#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.

Pack expansion

A pack expansion is an expression that contains one or more parameter packs followed by an ellipsis to indicate that the parameter packs are expanded. Consider the following example:
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

Example:
#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

Example:
#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

Example:
#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

Example:
#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

Example:
#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

Example:
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.
If a parameter pack is declared, it must be expanded by a pack expansion. An appearance of a name of a parameter pack that is not expanded is incorrect. Consider the following example:
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.
Pack expansion cannot match a parameter that is not a parameter pack. Consider the following example:
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...){};                
If more than one parameter pack is referenced in a pack expansion, each expansion must have the same number of arguments expanded from these parameter packs. Consider the following example:
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.

Partial specialization

Partial specialization is a fundamental part of the variadic templates feature. A basic partial specialization can be used to access the individual arguments of a parameter pack. The following example shows how to use partial specialization for variadic templates:
// 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.
A pack expansion must be the last argument in the argument list for a partial specialization. Consider the following example:
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.
A partial specialization can have more than one template parameter pack in its parameter list. Consider the following example:
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.
To access the arguments of a parameter pack, you can use partial specialization to access one member of the parameter pack in the first step, and recursively instantiate the remainder of the argument list to get all the elements, as shown in the following example:
#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 argument deduction

Parameter packs can be deduced by template argument deduction in the same way as other normal template parameters. The following example shows how template argument deduction expands packs from a function call:
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.
In this example, if you change the function call statement func(1,2,3,4,5,6) to func(), template argument deduction deduces that the template parameter pack A is empty:
template<class...A> void func(A...args){}

int main(void){
    func();
    return 0;
}
Template argument deduction can expand packs from a template instantiation, as shown in the following example:
#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.

Template argument deduction can expand packs after the explicit template arguments are found. Consider the following example:
#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.