Variadic templates
In C++ 11 and above, there is support for variadic templates as part of the standard language. A variadic template is a template class or template function that takes a variable number in a template argument. In classic C++, template instantiation happens with a fixed number of parameters. Variadic templates are supported both at class level and function level. In this section, we will deal with variadic functions as they are used extensively in writing functional-style programs, compile-time programming (meta programming), and pipeable functions:
//Variadic.cpp
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
using namespace std;
//--- add given below is a base case for ending compile time
//--- recursion
int add() { return 0; } // end condition
//---- Declare a Variadic function Template
//---- ... is called parameter pack. The compiler
//--- synthesize a function based on the number of arguments
//------ given by the programmer.
//----- decltype(auto) => Compiler will do Type Inference
template<class T0, class ... Ts>
decltype(auto) add(T0 first, Ts ... rest) {
return first + add(rest ...);
}
int main() { int n = add(0,2,3,4); cout << n << endl; }
In the preceding code, the compiler synthesizes a function based on the number of arguments passed. The compiler understands that add is a variadic function and generates the code by recursively unpacking the parameters during compile time. Compile time recursion will stop when the compiler has processed all the parameters. The base case version is a hint to the compiler to stop recursion. The next program shows how variadic templates and perfect forwarding can be used to write a function that takes an arbitrary number of parameters:
//Variadic2.cpp
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
using namespace std;
//--------- Print values to the console for basic types
//-------- These are base case versions
void EmitConsole(int value) { cout << "Integer: " << value << endl; }
void EmitConsole(double value) { cout << "Double: " << value << endl; }
void EmitConsole(const string& value){cout << "String: "<<value<< endl; }
The three variants of EmitConsole print the argument to the console. We have functions for printing int, double, and string. Using these functions as a base case, we will write a function that uses universal references and perfect forwarding to write functions that take arbitrary values:
template<typename T>
void EmitValues(T&& arg) { EmitConsole(std::forward<T>(arg)); }
template<typename T1, typename... Tn>
void EmitValues(T1&& arg1, Tn&&... args){
EmitConsole(std::forward<T1>(arg1));
EmitValues(std::forward<Tn>(args)...);
}
int main() { EmitValues(0,2.0,"Hello World",4); }