Variadic Templates
Templates that accept any number of arguments of any type
std::make_unique, vector::emplace_back, std::tuple and type-safe formatting. Everything is resolved by the compiler at instantiation time, so a variadic call has the same cost as the hand-written function it expands to: no varargs, no runtime type erasure, no heap.
- declare template and function parameter packs and query their length with
sizeof..., - expand a pack with the
...operator, including pattern expansion, - process a pack by recursion (head / tail) and know when a base case is needed,
- collapse a pack into one expression with C++17 fold expressions,
- forward a pack perfectly with
std::forward.
1. Parameter packs and sizeof...
Two kinds of pack work together. A template parameter pack is written with ... after typename (or a non-type or template parameter); a function parameter pack uses that pack as the type of a run of function parameters.
template <typename... Ts> // Ts is a TEMPLATE parameter pack (a list of types)
void probe(Ts... args) { // args is a FUNCTION parameter pack (a list of values)
static_assert(sizeof...(Ts) == sizeof...(args)); // both counts agree
}
A pack is not a type and not a value. It is a compile-time list that you can do exactly two things with: ask its length, or expand it. Its length comes from sizeof..., which is a constexpr of type std::size_t evaluated entirely at compile time - it counts the elements, it does not add their sizes:
template <typename... Ts>
void howMany(Ts... args) {
std::cout << "arity = " << sizeof...(args) << "\n"; // e.g. 3
}
howMany(1, 2.0, 'x'); // arity = 3 ; Ts = {int, double, char}
An empty pack is legal: howMany() compiles and prints arity = 0. That single edge case is the source of most variadic bugs, so keep it in mind from the start.