- Modern C++:Efficient and Scalable Application Development
- Richard Grimes Marius Bancila
- 556字
- 2021-06-10 18:28:09
Variadic templates
A variadic template is when there is a variable number of template parameters. The syntax is similar to variable arguments to a function; you use ellipses, but you use them on the left of the argument in the parameter list, which declares it a parameter pack:
template<typename T, typename... Arguments>
void func(T t, Arguments... args);
The Arguments template parameter is zero or more types, which are the types of the corresponding number of arguments, args, of the function. In this example, the function has at least one parameter, of type T, but you can have any number of fixed parameters, including none at all.
Within the function, you need to unpack the parameter pack to get access to the parameters passed by the caller. You can determine how many items there are in the parameter pack using the special operator, sizeof... (note the ellipses are part of the name); unlike the sizeof operator, this is the item count and not the size in bytes. To unpack the parameter pack, you need to use the ellipses on the right of the name of the parameter pack (for example, args...). The compiler will expand the parameter pack at this point, replacing the symbol with the contents of the parameter pack.
However, you will not know at design time how many parameters there are or what types they are, so there are some strategies to address this. The first uses recursion:
template<typename T> void print(T t)
{
cout << t << endl;
}
template<typename T, typename... Arguments>
void print(T first, Arguments ... next)
{
print(first);
print(next...);
}
The variadic templated print function can be called with one or more parameters of any type that can be handled by the ostream class:
print(1, 2.0, "hello", bool);
When this is called, the parameter list is split into two: the first parameter (1) in the first parameter, first, and the other three are put in the parameter pack, next. The function body then calls the first version of print which, prints the first parameter to the console. The next line in the variadic function then expands the parameter pack in a call to print, that is, this calls itself recursively. In this call, the first parameter will be 2.0, and the rest will be put in the parameter pack. This continues until the parameter pack has been expanded so much that there are no more parameters.
Another way to unpack the parameter pack is to use an initializer list. In this case, the compiler will create an array with each parameter:
template<typename... Arguments>
void print(Arguments ... args)
{
int arr [sizeof...(args)] = { args... };
for (auto i : arr) cout << i << endl;
}
The array, arr, is created with the size of the parameter pack and the unpack syntax used with the initializer braces will fill the array with the parameters. Although this will work with any number of parameters, all the parameters have to be the same type of the array, arr.
One trick is to use the comma operator:
template<typename... Arguments>
void print(Arguments ... args)
{
int dummy[sizeof...(args)] = { (print(args), 0)... };
}
This creates a dummy array called dummy. This array is not used, other than in the expansion of the parameter pack. The array is created in the size of the args parameter pack and the ellipsis expands the parameter pack using the expression between the parentheses. The expression uses the comma operator, which will return the right side of the comma. Since this is an integer, it means that each entry of dummy has a value of zero. The interesting part is the left side of the comma operator. Here the version of print with a single templated parameter is called with each item in the args parameter pack.