Variadic Templates
Contents
Introduction
We could pass an unknown number of arguments in C++ using the "va_list" approach. The C++ 11 standard gives us variadic templates. It uses a template approach.Printf Example
File: var1.cpp
#include <iostream> using namespace std ; template<typename T> void printf1(T v) { cout << "Single argument function:" << v << endl ; } //template<typename T> /* void printf1() { cout << "Empty printf." << endl ; } */ template<typename T, typename... Args> void printf1(T first, Args... args) { cout << "first:" << first << endl ; // printf1( first ) ; //cout << "Enter:" << endl ; printf1( args... ) ; } int main() { printf1( 1, 2, 3 ); //printf1( "1", 2, 3 ); cout << "End of main." << endl ; } Output: $ rm a.exe ; g++ var1.cpp ; ./a.exe first:1 first:2 Single argument function:3 End of main.
We define a function: templatevoid printf1(T first, Args... args) { We have to use the word "typename" and use "..." in the argument to signify any number of arguments. We call it like: printf1( 1, 2, 3 ); When the template function is called then the arguments are split up as: first=1 args= 2,3 We print the first argument and then recursively call the same function name. printf1( args... ) ; This again splits the arguments into first=2 args= 3 We print the 2 and then we call the "printf1" with 3. However now the printf1 only has a single argument so it calls the overloaded function that we defined earlier that only takes a single argument. What if we did not have the single defined function.
File: var2.cpp
#include <iostream> using namespace std ; /* template<typename T> void printf1(T v) { cout << "Single argument function:" << v << endl ; } */ //template<typename T> /* void printf1() { cout << "Empty printf." << endl ; } */ template<typename T, typename... Args> void printf1(T first, Args... args) { cout << "first:" << first << endl ; // printf1( first ) ; //cout << "Enter:" << endl ; printf1( args... ) ; //LABEL A } int main() { printf1( 1, 2, 3 ); //printf1( "1", 2, 3 ); cout << "End of main." << endl ; } $ rm a.exe ; g++ var2.cpp ; ./a.exe var2.cpp: In instantiation of ‘void printf1(T, Args ...) [with T = int; Args = {}]’: var2.cpp:29:10: recursively required from ‘void printf1(T, Args ...) [with T = int; Args = {int}]’ var2.cpp:29:10: required from ‘void printf1(T, Args ...) [with T = int; Args = {int, int}]’ var2.cpp:35:9: required from here var2.cpp:29:10: error: no matching function for call to ‘printf1()’ 29 | printf1( args... ) ; | ~~~~~~~^~~~~~~~~~~~~ var2.cpp:23:6: note: candidate: ‘templatevoid printf1(T, Args ...)’ 23 | void printf1(T first, Args... args) | ^~~~~~~ var2.cpp:23:6: note: template argument deduction/substitution failed: var2.cpp:29:10: note: candidate expects at least 1 argument, 0 provided 29 | printf1( args... ) ; | ~~~~~~~^~~~~~~~~~~~~ bash: ./a.exe: No such file or directory
We received a compiler error. We had the arguments 1,2 and 3. This got split up as: 1 2,3 and then 2 3 and then say we printed out 2 and then we called "printf1" at "LABEL A" printf 3 Now the "first" is 3 and the args is empty. We print out 3 and then call the "printf1" at "LABEL A" with an empty args. However we only have 1 "printf1" function and that expects at least 1 argument that is assigned to "first". The compiler error states: var2.cpp:23:6: note: template argument deduction/substitution failed: var2.cpp:29:10: note: candidate expects at least 1 argument, 0 provided 29 | printf1( args... ) ; Ok why don't we define an empty "printf1" function and see if that resolves the issue.
File: var3.cpp
#include <iostream> using namespace std ; /* template<typename T> void printf1(T v) { cout << "Single argument function:" << v << endl ; } */ //template<typename T> void printf1() { cout << "Empty printf." << endl ; } template<typename T, typename... Args> void printf1(T first, Args... args) { cout << "first:" << first << endl ; // printf1( first ) ; //cout << "Enter:" << endl ; printf1( args... ) ; //LABEL A } int main() { printf1( 1, 2, 3 ); //printf1( "1", 2, 3 ); cout << "End of main." << endl ; } Output: $ rm a.exe ; g++ var3.cpp ; ./a.exe rm: cannot remove 'a.exe': No such file or directory first:1 first:2 first:3 Empty printf. End of main. This resolves the issue and we see that the last call to the "printf1" went to the empty function. Notice we did not make the "printf" function a template function. Since we are not taking any arguments; how is the system going to determine the type for the template ? We will get a copmpiler error with the statement: templatevoid printf1() { cout << "Empty printf." << endl ; }
File: var4.cpp
#include <iostream> using namespace std ; template<typename T> void printf1(T v) { cout << "Single argument function:" << v << endl ; } //template<typename T> /* void printf1() { cout << "Empty printf." << endl ; } */ template<typename T, typename... Args> void printf1(T first, Args... args) { cout << "first:" << first << endl ; // printf1( first ) ; //cout << "Enter:" << endl ; printf1( args... ) ; //LABEL A } int main() { //printf1( 1, 2, 3 ); printf1( "One", 2, 3 ); cout << "End of main." << endl ; } $ rm a.exe ; g++ var4.cpp ; ./a.exe first:One first:2 first:3 Empty printf. End of main. The program "var4.cpp" shows that our program works for arguments that are of different types as well.
Exercises
Fill in the code for the "adder" functions.
File: var_ex1.cpp
Solutions
File: var_soln1.cpp