IBM Z and LinuxONE - Languages - Group home

C++ Templates What is a variadic template function?

By FANG LU posted Tue March 24, 2020 07:39 PM

  

The Variadic Template Function


The latest language standard has further complicated C++ by adding variadic templates. One problem I had in my attempt to understand this new feature was the lack of simple examples showing how it worked. Here's my attempt to show some of the basics of a variadic template function.


template <class ...A> int func(A... arg){   return sizeof...(arg);}int main(void){   return func(1,2,3,4,5,6);}


First the basic vocabulary: A template parameter can now be a template parameter pack <class...A>. A template parameter pack represents any number of template parameters. In this example the template definition now has defined func with any number of template parameters.


The function argument (A... arg) is known as a function parameter pack and it represents one argument for every member of the indicated template parameter pack.


In the example, I call the function with 6 arguments. Template argument deduction then deduces parameter pack <class...A> to be <int,int,int,int,int,int> and the function parameter pack becomes (int,int,int,int,int,int) corresponding to the 6 integers passed in the call.


The variadic sizeof... simply returns the number of elements in the parameter pack (function or template). In this case we return 6.

 

Any parameter pack can be empty. Consider this similar example:

template <class ...A> int func(A... arg){   return sizeof...(arg);}int main(void){   return func();}


In this case the template parameter pack is deduced to be empty which leaves the function parameter pack empty and we can call the function with no arguments and get 0 for our size.

 

The elements of the parameter pack do not need to be the same type:

template <class ...A> int func(A... arg){   return sizeof...(arg);}struct s1{}; struct s2{}; struct s3{};int main(void){   s1 t1; s2 t2; s3 t3;   return func(t1,t2,t3);}


In this case the parameter pack is deduced to <s1,s2,s3>.

 

In the previous examples, the parameter pack for for a type parameter, but you can also have a non-type parameter pack as in this example:

template <bool ...A> int func(){   return sizeof...(A);}int main(void){   return func<true,false,true,false>();}


These simple examples show the syntax. Counting the number of elements is cute, but the pack is not much use if the elements cannot be accessed.

To access the elements of the parameter pack in a function template, overload the function:

#include <iostream>using namespace std;void func(){   cerr << "EMPTY" << endl;}template <class A, class ...B> void func(A argHead, B... argTail){   cerr << "A: " << argHead << endl;   func(argTail...);}int main(void){   func(1,2,3,4,5,6);   return 0;}



In this example, there are 2 candidate functions matching the call. In this case one is a non-template. Since the non-template candidate has no function parameters, this will only match a function call func(), all other calls need to match the template candidate.


In order to match a template candidate function the compiler has to deduce the template parameters from the function call arguments, then it instantiates the template function.

This template function has its first argument based on an ordinary template parameter <class A> and its second argument based on a parameter pack. The second argument is a function parameter pack. The first template parameter will be deduced to the type of the first argument. The type of each member of the parameter pack will be based on all the remaining arguments.


The whole thing begins with the function call in main func(1,2,3,4,5,6);

Template parameter <class A> is deduced to <int> and template parameter pack <class...B> is deduced to <int,int,int,int,int>


The compiler instantiates the function template void func(int,int,int,int,int,int);

Inside the function body of that instantiation, argTail... is a pack expansion. A pack expansion is expanded by the compiler according its context. In this case argTail is (2,3,4,5,6).


The first argument is argHead which is now a normal function parameter.

We now continue to the function call func(argTail...) with argTail... expanded, this function call is func(2,3,4,5,6), we begin argument deduction all over.


Template parameter <class A> is deduced to <int> and template parameter pack <class...B> is deduced to <int,int,int,int>.

Inside the function argHead is now (2) and argTail is (3,4,5,6) and the new function call is func(3,4,5,6)

We now continue to the function call func(argTail...) with argTail... expanded, this function call is func(3,4,5,6), we begin argument deduction all over.


Template parameter <class A> is deduced to <int> and template parameter pack <class...B> is deduced to <int,int,int>.

Inside the function argHead is now (3) and argTail is (4,5,6) and the new function call is func(4,5,6)

We now continue to the function call func(argTail...) with argTail... expanded, this function call is func(4,5,6), we begin argument deduction all over.

Template parameter <class A> is deduced to <int> and template parameter pack <class...B> is deduced to <int,int>.

Inside the function argHead is now (4) and argTail is (5,6) and the new function call is func(5,6)

We now continue to the function call func(argTail...) with argTail... expanded, this function call is func(5,6), we begin argument deduction all over.

Template parameter <class A> is deduced to <int> and template parameter pack <class...B> is deduced to <int>.

Inside the function argHead is now (5) and argTail is (6) and the new function call is func(6)

We now continue to the function call func(argTail...) with argTail... expanded, this function call is func(6), we begin argument deduction all over.


Template parameter <class A> is deduced to <int> and template parameter pack <class...B> is deduced to <>, the parameter pack is now empty.


Inside the function argHead is now (6) and argTail is () and the new function call is func()

function call func() now matches the non-template candidate.

Output at runtime is:

A: 1

A: 2

A: 3

A: 4

A: 5

A: 6

EMPTY

0

0 comments
2 views