Variadic Templates
Variadic function templates in C++
Variadic templates are class or function templates that can take any variable (zero or more) number of arguments. In C++, templates can have a fixed number of parameters only that have to be specified at the time of declaration. However, variadic templates help to overcome this issue. Douglas Gregor and Jaakko Järvi came up with this feature for C++.
Variadic arguments are very similar to arrays in C++. We can easily iterate through the arguments, find the size (i.e. length) of the template, can access the values by an index, and can slice the templates too.
So basically, Variadic function templates are functions that can take multiple numbers of arguments.
Syntax:
template <typename arg, typename... Args> return_type function_name(arg var1, Args... args)
The ellipsis (...) makes Args or args a so-called parameter pack. To be precise, Args is a template parameter pack and args a function parameter pack. Two operations are possible with parameter packs. They can be packed and unpacked. If the ellipse is to the left of Args, the parameter pack will be packed; if it is to the right of Args, it will be unpacked. Because of the function template argument deduction, the compiler can derive the template arguments.
Below is an example in C++ to show how we can use a variadic function template:
// C++ program to demonstrate working of
// Variadic function Template
#include <iostream>
using namespace std;
// To handle base case of below recursive
// Variadic function Template
void print()
{
cout << "I am empty function and "
"I am called at last.\n";
}
// Variadic function Template that takes
// variable number of arguments and prints
// all of them.
template <typename T, typename... Types>
void print(T var1, Types... var2)
{
cout << var1 << endl;
print(var2...);
}
// Driver code
int main()
{
print(1, 2, 3.14,
"Pass me any "
"number of arguments",
"I will print\n");
return 0;
}
Output:
1 2 3.14 Pass me any number of arguments I will print I am empty function and I am called at last.
Explanation: The variadic templates work as follows:
The statement, print(1, 2, 3.14, "Pass me any number of arguments", "I will print\n"); is evaluated in the following manner:
Firstly, the compiler resolves the statement into
cout << 1 << endl ;
print(2, 3.14, "Pass me any number of arguments",
"I will print\n");
Now, the compiler finds a print() function which can take those arguments and in result executes the variadic print() function again in a similar manner. And so on.
Variadic Templates in the STL
Variadic templates are often used in the Standard Template Library and the core language.
template <typename... Types> // (1) class tuple; template <typename Callable, typename... Args > // (2) explicit thread(Callable&& f, Args&&... args); template <typename Lockable1, typename Lockable2, typename... LockableN> // (3) void lock(Lockable1& lock1, Lockable2& lock2, LockableN&... lockn); sizeof...(ParameterPack);
sizeof... Operator
The sizeof... operator can be used to determine how many elements a parameter pack contains directly. The elements are not evaluated.
// printSize.cpp
#include <iostream>
using namespace std::literals;
template <typename ... Args>
void printSize(Args&& ... args){
std::cout << sizeof...(Args) << ' '; // (1)
std::cout << sizeof...(args) << '\n'; // (2)
}
int main() {
std::cout << '\n';
printSize(); // (3)
printSize("C string", "C++ string"s, 2011, true); // (4)
std::cout << '\n';
}
Usage of Parameter Packs
(From https://www.modernescpp.com/index.php/more-arbout-variadic-templates/, by Rainer Grimm)
The usage of parameter packs obeys a typical pattern for class templates.
- Operate on the first element of the parameter pack and recursively invoke the operation on the remaining elements. This step reduces the parameter pack successively by its first element.
- The recursion ends after a finite number of steps.
- The boundary condition is typically a fully specialized template.
Thanks to this functional pattern for processing lists, you can calculate the product of numbers at compile time, for instance.
// multVariadicTemplates.cpp
#include <iostream>
template<int ...> // (1)
struct Mult;
template<> // (2)
struct Mult<> {
static const int value = 1;
};
template<int i, int ... tail> // (3)
struct Mult<i, tail ...> {
static const int value = i * Mult<tail ...>::value;
};
int main(){
std::cout << '\n';
std::cout << "Mult<10>::value: " << Mult<10>::value << '\n'; // (4)
std::cout << "Mult<10,10,10>::value: " << Mult<10, 10, 10>::value << '\n';
std::cout << "Mult<1,2,3,4,5>::value: " << Mult<1, 2, 3, 4, 5>::value << '\n';
std::cout << '\n';
}
The class template Mult consists of a primary template and two specializations. Since the primary template (1) is not needed, a declaration is sufficient in this case: template<int ...> struct Mult. The specializations of the class template exist for no element (2) and at least one element (3). If Mult<10,10,10>::value is called, the last template is used by successively calling the first element with the rest of the parameter pack so that the value expands to the product 10*10*10. In the final recursion, the parameter pack contains no elements, and the boundary condition comes into action: template<> struct Mult<> (1). This returns the result of Mult<10,10,10>::value= 10*10*10*1 at compile time.
Variadic Templates and Perfect Forwarding
Forwarding parameters is a way to receive all the arguments received by a function and resend them as they come. This is achieved through variadic templates, which allow the programmer to pass an undefined number of arguments to a function/method.
The code that follows combines two new features of the C++11, constexpr and variadic parameters. constexpr allows making computations at compile time if they are feasable (there are several conditions to meet, but for now, we just assume that they are fulfilled). In this example, we find the minimum of a list of arguments at compile time (thanks to constexpr!). This is a generic version that works only with integers but works on 2, 3, 4, ... n arguments.
constexpr int min(int n, int p)
{
return n < p ? n : p;
}
template<typename... Args> constexpr int min(int n, Args... args)
{
return min(n, min(args...));
}
int main(int argc, char *argv[])
{
static_assert(min(4, 5, 6, 42, 7, 3, 6) == 3, "min is incorrect");
return 0;
}
Forwarding is done through the std::forward function from the utility header. It forwards the arguments exactly as they are received (more information in the man). Here is a little example of how it works:
#include <iostream>
#include <utility>
struct Item
{
Item(): value{0} {}
Item(const Item& p){ std::cout << "Copy" << std::endl; }
int value;
};
void take_args(int& a, int&& b, int c, Item& f)
{
std::cout << a << " - " << b << " - " << c
<< " - " << f.value << std::endl;
}
template <typename... Args>
void call_take_args(Args&&... args)
{
take_args(std::forward<Args>(args)...);
}
int main()
{
Item f;
int i = 2;
call_take_args(i, 4, 5, f);
}
// The program outputs "2 - 4 - 5 - 0".