Contents
Generic programming and metaprogramming with templates always have been one of the features that set C++ apart from most other languages. With C++11, variadic templates added even more possibilities in that area to the language.
Classic templates had a fixed number of arguments. However, in some use cases, it is desirable to have the same function or class template for varying numbers of template parameters. The only possible solution to achieve this in C++03 was to write the same template over and over for 0, 1, 2, … N parameters with a reasonably large N.
It was possible to achieve some automation by using macros, e.g. with Boost.Preprocessor, but it takes some masochistic tendencies to learn the necessary techniques. In addition, those macros had the usual weaknesses of not being debuggable.
Variadic templates to the rescue
With C++11 we got variadic templates to allow defining a single template that can take an arbitrary number of parameters. Basically, two new syntactic elements are needed for the definition of those templates: Being able to define a so-called parameter pack while declaring the template, and expanding the pack in the template’s definition.
Parameter pack
A parameter pack is simply a name assigned to a list of template parameters instead to a single parameter. There are three kinds of template parameters, and there is a way to define a parameter pack instead of a single template parameter for all three of them.
template <class... Ts> //Ts is a list of type parameters
template <unsigned... Ns> //Ns is a list of non-type parameters (unsigned)
template <template <class T>... class Us> //Us is a list of template template parameters
As with “normal” templates, variadic templates can be function templates and class templates, and they can be specialized etc. We can also mix single parameters and parameter packs, with the restriction, that there may only be a single parameter pack definition, and it has to be at the end of the parameter list:
template <class X, int I, class... Ts>
You notice that I use names like Ts
, Ns
and Us
for parameter packs. You will also often see names like Args
. It is just a good naming convention, but not required, to use plural names for parameter packs.
In addition to template parameter packs we have function parameter packs. They are defined when in a variadic function template the template parameter pack is used to define function arguments. Sounds complicated, but an example will clear this up:
template <class... Args> //Args is the template parameter pack
void f(int i, Args... args) { //args is the function parameter pack
//...
}
Pack expansion
It is not possible to use a parameter pack except to expand it. In most cases, parameter pack expansion yields a comma separated list of expressions containing the single elements of the pack. The simplest pack expansion is just the pack name followed by an ellipsis, which results in a comma-separated list of the pack elements:
template <class... Args>
void f(int i, Args... args) {
//expand template parameter pack Args first, then function parameter pack args
std::tuple<Args...> argsTuple{args...};
//...
}
f(21, 54.3, "foo", 47u);
In the function call to f
, 21
is the int
parameter, and the other three parameters define the two parameter packs. The template parameter pack Args
will be the list of the types double
, char const*
and unsigned
, while the function parameter pack args
will be the list of the values 54.3
, "foo"
and 47u
.
This single instantiation of the function template will essentially be as if we had written
void f(int i, double args_1, char const* args_2, unsigned args_3) {
std::tuple<double, char const*, unsigned> argsTuple{args_1, args_2, args_3};
//...
}
and the call simply sets args_1
to 54.3
and so on. As you may know or have guessed by now, std::tuple
itself is a variadic class template.
More pack expansion
Having the ellipsis directly after the parameter pack name is the simplest form of pack expansion. It can be way more complicated than that, though. In principle, we can write any pattern containing a parameter pack name and have it followed by an ellipsis. The result will be a comma-separated list of patterns, where in each pattern the pack name is replaced by one member of the pack.
Take for example the tuple we used above. Normally we would want to use perfect forwarding to create the tuple from the function arguments. Let’s have a look:
template <class... Args>
void f(int i, Args&&... args) {
std::tuple<Args...> argsTuple{std::forward<Args>(args)...};
//...
}
We have three pack expansions here: Args&&...
means we have a list of forwarding references. The Args...
expansion for the std::tuple
template parameters is the one we had before. The third expansion is std::forward<Args>(args)...
and contains two parameter packs: The template parameter pack Args
and the function parameter pack args
. Whenever two packs appear in the pattern for a pack expansion, both will get expanded simultaneously and therefore need to have the same number of elements.
The above function in pseudo-template code after pack expansion would look like this:
template <class Args_1, class Args_2, /* and so on... */>
void f(int i, Args_1&& args_1, Args_2&& args_2, /*...*/) {
std::tuple<Args_1, Args_2, /*...*/> argsTuple{std::forward<Args_1>(args_1), std::forward<Args_2>(args_2), /*...*/};
//...
}
Empty packs
In the beginning of this post, I wrote that a variadic template can take an arbitrary number of parameters. This includes 0, i.e. there may be no parameters at all. In those cases, the pack expansion yields an empty list. In cases where the pack expansion is preceded by a comma, that comma is ignored. Taking our above example again, the call f(22)
would yield a (pseudo) template instantiation like this:
template<>
void f(int i /*, ignored comma before empty pack expansion*/) {
std::tuple<> argsTuple{}; //lots of empty packs...
}
More to come
This is it for today, we only scratched the surface of variadic templates. I’ll cover more next week, with more examples and ways to use variadic templates and so on. Stay tuned!
Permalink
Permalink
Permalink
Permalink
Permalink
Great introduction to variadic templates.
Vielen Dank, homie!
Permalink
If anybody wants to know why this is useful, look at https://github.com/dascandy/hippomocks/blob/master/HippoMocks/hippomocks.h#L3265 – from this line on, this function is repeated 170 times. It only changes one argument’s type – from a regular function pointer with 0..16 arguments, to a member function pointer with 0..16 arguments, to a __stdcall const volatile member function pointer, to …. all of the other variants in there.
Variadic templates cut this down to 10.