Advanced C++ Variadic Templates | Pack Expansion and Fold Expressions

Advanced C++ Variadic Templates | Pack Expansion and Fold Expressions

이 글의 핵심

Advanced variadic templates: pack expansion, fold expressions, and patterns you will use in real code.

Advanced variadic templates

A variadic template accepts a variable number of types or values. You pack them with ...Args, then unpack with recursion or fold expressions (C++17). Pair this with template basics and template specialization for stronger designs.

Packing and recursion

To accept variadic arguments, declare a parameter pack and split work between a base case (zero arguments) and a recursive case (first argument + rest of the pack).

#include <iostream>

// Base case
void print() {
    std::cout << '\n';
}

// Recursive case
template<typename T, typename... Rest>
void print(T first, Rest... rest) {
    std::cout << first << ' ';
    print(rest...);  // expand rest and recurse
}

int main() {
    print(1, 2.0, "three");
    // output: 1 2 three
    return 0;
}

How it works:

  1. print(1, 2.0, "three"): T = int, Rest = {double, const char*}
  2. Print 1, then print(2.0, "three")
  3. Continue until Rest is empty, then print() (base)

Tip: Rest... is “the remaining types”; rest... expands the remaining arguments for the next print call. A fold expression can replace this recursion in one line.

template<typename... Args>
void print_fold(Args... args) {
    (std::cout << ... << args) << '\n';
}

print_fold(1, 2.0, "three");  // no explicit recursion

Fold expressions (C++17)

A fold applies a binary operator across the whole pack. (args + ...) is a unary right fold, equivalent to args_1 + (args_2 + (args_3 + ...)).

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

template<typename... Args>
bool all(Args... args) {
    return (args && ...);
}

// Empty pack: use a binary fold with an initializer
template<typename... Args>
auto sum_with_zero(Args... args) {
    return (0 + ... + args);  // empty pack => 0
}

Use cases include logging, small utility wrappers, and type-list processing.

Pack expansion patterns

Expanding a pack as (expr)... repeats expr for each element.

#include <iostream>
#include <vector>

template<typename... Args>
void call_functions(Args... args) {
    (process(args), ...);  // process(arg1), process(arg2), ...
}

template<typename... Args>
void add_to_vector(std::vector<int>& v, Args... args) {
    (v.push_back(args), ...);
}

template<typename... Args>
void log(Args&&... args) {
    (std::cout << ... << args) << '\n';
}

Expansion patterns:

PatternMeaningExample
args...Unpack packf(args...)f(a1, a2, a3)
(expr(args))...Per-element expression(f(args))...
(args + ...)Unary right fold
(... + args)Unary left fold
(init + ... + args)Binary fold

Examples:

template<typename... Args>
void set_values(int& result, Args... args) {
    result = (args + ...);
}

template<typename... Args>
bool all_true(Args... args) {
    return (args && ...);
}

template<typename... Funcs>
void execute_all(Funcs... funcs) {
    (funcs(), ...);
}

Example: type-safe tuple indexing idea

You can build a metafunction that returns the type at index I from a type list—useful with structured bindings.

template<typename... Ts>
struct type_list {};

template<size_t I, typename T>
struct type_at_impl;

template<typename T, typename... Rest>
struct type_at_impl<0, type_list<T, Rest...>> {
    using type = T;
};

template<size_t I, typename T, typename... Rest>
struct type_at_impl<I, type_list<T, Rest...>> {
    using type = typename type_at_impl<I - 1, type_list<Rest...>>::type;
};
  • Template template parameters
  • auto type deduction

Common issues

  • Empty packs: Some operators are ill-formed on empty unary folds; use a binary fold with an initializer or if constexpr (sizeof...(args) > 0).
  • Expansion context: (args)... vs (args...) differ—parentheses group what repeats.

Summary

TopicNotes
Parameter packtypename... Args, Args... args
Fold(args op ...), (... op args), (init op ... op args)
PracticeLogging, wrappers, type lists, variadic adapters

In one sentence: Variadic templates let you write APIs independent of arity; C++17 folds often replace recursion.


  • Variadic templates series
  • Fold expressions
  • Template basics
  • Template specialization
  • Template template parameters

Keywords

C++, variadic template, parameter pack, fold expression, C++17.

Checklist

Before coding

  • Is this the best approach?
  • Can the team maintain it?
  • Does it meet performance goals?

While coding

  • Warnings addressed?
  • Edge cases covered?
  • Errors handled?

At review

  • Clear intent?
  • Enough tests?
  • Documented?

FAQ

Q. Where is this used in production?

A. Logging, generic adapters, type lists, and any API that must accept a variable number of types or values—see the patterns above.

Q. What should I read first?

A. Follow Related posts at the bottom of each article, or use the C++ series index.

Q. Go deeper?

A. cppreference and library docs are the authoritative references.