CppVariadic Templates

Variadic Templates

A variadic template (introduced in C++11) accepts any number of template arguments, of any types — not just one or two fixed parameters. This is the mechanism behind functions like std::make_unique, std::make_shared, and std::vector::emplace_back, all of which need to forward an arbitrary list of constructor arguments.
Parameter packs
A parameter pack is written with an ellipsis: typename... Args declares a pack of zero or more types named Args, and Args... args declares a matching pack of function parameters named args.

variadic_basics.cpp

CPP
template <typename... Args>
void doSomething(Args... args) {
    // args can be zero or more values, of any types
}
The classic example: a recursive print
The traditional way to process a parameter pack is recursively: peel off the first argument, handle it, then recurse on the rest of the pack until it's empty.

variadic_print.cpp

CPP
#include <iostream>

// Base case: no arguments left, stop the recursion.
void print() {
    std::cout << std::endl;
}

// Recursive case: handle the first argument, recurse on the rest.
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
    std::cout << first << " ";
    print(rest...);   // "unpacks" one fewer argument each call
}

int main() {
    print(1, 2.5, "three", 'f');  // prints: 1 2.5 three f
    print();                       // prints nothing but a newline
    return 0;
}
Each call to print peels off one argument (first) and forwards the remaining pack (rest...) to the next call. The compiler generates one overload of print for every distinct combination of argument count and types actually used — the recursion happens at compile time, unrolling into a fixed chain of function calls, not a runtime loop.
sizeof...(Args) — counting the pack
The sizeof... operator returns the number of elements in a parameter pack, evaluated at compile time.

sizeof_pack.cpp

CPP
#include <iostream>

template <typename... Args>
void countArgs(Args... args) {
    std::cout << "Received " << sizeof...(Args) << " arguments" << std::endl;
}

int main() {
    countArgs(1, 2, 3);         // Received 3 arguments
    countArgs("a", 'b');        // Received 2 arguments
    countArgs();                // Received 0 arguments
    return 0;
}
C++17 fold expressions — a simpler alternative
For many common patterns, C++17 fold expressions let you collapse an entire parameter pack with a single operator, avoiding the recursive helper function entirely.

fold_expression.cpp

CPP
#include <iostream>

// A "unary right fold" over the pack using the comma operator.
template <typename... Args>
void printFold(Args... args) {
    ((std::cout << args << " "), ...);
    std::cout << std::endl;
}

// A fold using + to sum every argument in the pack.
template <typename... Args>
auto sumAll(Args... args) {
    return (args + ...);
}

int main() {
    printFold(1, 2.5, "three");  // 1 2.5 three
    std::cout << sumAll(1, 2, 3, 4) << std::endl; // 10
    return 0;
}
Recursion vs. fold expressions
Before C++17, the recursive base-case-plus-recursive-case pattern shown above was the only way to process a parameter pack, and you'll still see it constantly in older code and in library internals. For simple “apply this operator to everything in the pack” cases, a fold expression is shorter, avoids writing a separate base-case overload, and is usually the better choice in new C++17-or-later code.
Key points
  • template <typename... Args> declares a type parameter pack; Args... args declares a matching function parameter pack.

  • Packs are typically processed recursively (peel off one argument, recurse on the rest) or, since C++17, with a fold expression.

  • sizeof...(Args) (or sizeof...(args)) gives the number of elements in a pack at compile time.

  • Variadic templates power std::make_unique, std::make_shared, emplace_back, and much of the rest of modern generic C++ code.