Variadic Templates — C++

What are Variadic Templates?

Introduced in C++11, variadic templates accept any number of type arguments. They power std::tuple, std::make_unique, std::forward, and many other standard library facilities.


Parameter Pack Syntax

template <typename... Args>   // template parameter pack
void print_all(Args... args); // function parameter pack

// sizeof... gets the count
template <typename... Args>
constexpr std::size_t count() { return sizeof...(Args); }

Pack Expansion

Appending ... expands a pack in the appropriate context:

template <typename... Args>
void forward_all(Args&&... args) {
    some_func(std::forward<Args>(args)...); // expands both packs
}

Recursive Variadic Templates

Classic C++11 pattern: base case + recursive case:

// Base case
void print() {}

// Recursive case
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
    std::cout << first << " ";
    print(rest...);   // recurse with remaining args
}
// Usage: print(1, "hello", 3.14);

[!NOTE] C++17 fold expressions replace most recursive variadic templates with shorter, clearer code.


Fold Expressions (C++17)

Fold expressions collapse a pack using a binary operator in a single expression:

// Unary right fold: (args op ...)
template <typename... Args>
auto sum(Args... args) { return (args + ...); }

// Unary left fold: (... op args)
template <typename... Args>
auto sum_left(Args... args) { return (... + args); }

// Binary fold with initial value
template <typename... Args>
auto sum_with_init(Args... args) { return (0 + ... + args); }

// Print with fold
template <typename... Args>
void print_all(Args&&... args) {
    ((std::cout << args << " "), ...);
}

sizeof…(Args)

template <typename... Args>
void show_count(Args... args) {
    std::cout << "Pack has " << sizeof...(args) << " elements\n";
}

Common Use Cases

Perfect forwarding wrapper:

template <typename T, typename... Args>
std::unique_ptr<T> make(Args&&... args) {
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

[!TIP] This pattern is exactly how std::make_unique and std::make_shared are implemented in the standard library.


Variadic Template Instantiation

flowchart TD A[“print(1, hello, 3.14)”] –> B[“Instantiate print(int, string, double)”] B –> C[“Print 1, call print(hello, 3.14)”] C –> D[“Instantiate print(string, double)”] D –> E[“Print hello, call print(3.14)”] E –> F[“Instantiate print(double)”] F –> G[“Print 3.14, call print()”] G –> H[“Base case: print() - done”]

Fold Expression Types

flowchart LR A[“Fold Expressions (C++17)”] –> B[“Unary Right Fold”] A –> C[“Unary Left Fold”] A –> D[“Binary Right Fold”] A –> E[“Binary Left Fold”] B –> B1[“(args op …)”] C –> C1[“(… op args)”] D –> D1[“(args op … op init)”] E –> E1[“(init op … op args)”]


Summary Table

FeatureSyntaxC++ VersionUse case
Parameter packtypename... ArgsC++11Accept any number of types
Pack expansionargs...C++11Expand pack in expression
sizeof...sizeof...(Args)C++11Count pack elements
Recursive variadicbase + recursive fnC++11Process each element
Unary fold(args op ...)C++17Reduce pack with operator
Binary fold(init op ... op args)C++17Reduce with initial value