Skip to content
Theory · Templates · lesson 9 of 11

Variadic templates: a first look

The function double average(double a, double b) computes the average of two numbers. Three numbers would need one more overload, and five numbers another one. A template with an ellipsis after typename takes any number of arguments of any types:

C++
#include <iostream>

template <typename... Ts>
double average(Ts... xs) {
    return (xs + ... + 0.0) / sizeof...(Ts);
}

int main() {
    std::cout << average(1, 2) << ' ' << average(1, 2.5, 4) << '\n';  // 1.5 2.5
}

Parameter pack

Such a template is called a variadic template. The notation typename... Ts declares a parameter pack: not one type but a list of types of any length, empty included. In the function's parameter list, Ts... xs is a pack of values, one for each type in Ts. For the call average(1, 2.5, 4), the compiler deduces Ts as int, double, int, and xs holds three values.

For each set of types, the compiler instantiates a separate function: average(1, 2) and average(1, 2.5, 4) call two different functions. sizeof...(Ts) is the number of elements in the pack, not a size in bytes as with sizeof. For average(1, 2.5, 4) it is 3, and for an empty pack it is 0. The value is known at compile time.

Fold expression

A pack cannot be walked with a loop: its elements may have different types. Instead of a loop you write a fold expression: the pack, an operator and an ellipsis in required parentheses. For three values, (xs + ... + 0.0) expands to x1 + (x2 + (x3 + 0.0)), where x1, x2 and x3 are the elements of xs. The last operand 0.0 is the initial value: for an empty pack, the expression equals it.

The initial value also affects the type of the sum. With 0, the sum of two int values would stay an int, and average(1, 2) would return 1: dividing integers drops the fraction. With 0.0, the sum has the type double, and the result is 1.5.

The return type of such a function is not always known in advance. Then auto takes its place, and the compiler deduces the type from return, as with a lambda: auto f() { return 1 + 2.5; } returns a double.

Common mistake

The fold is written without an initial value:

C++
#include <iostream>

template <typename... Ts>
double total(Ts... xs) {
    return (xs + ...);  // ✗
}

int main() {
    std::cout << total(1, 2) << total() << '\n';
}
TEXT
error: unary fold expression has empty expansion for operator '+' with no fallback value

For total(1, 2) such a fold works, but for total() the pack is empty and there is nothing to add. The compiler reports the error when it instantiates total(). Add an initial value: (xs + ... + 0.0).