C++ Fold Expressions | Folding Parameter Packs in C++17

C++ Fold Expressions | Folding Parameter Packs in C++17

이 글의 핵심

Fold expressions let you apply an operator across a parameter pack in one expression—often replacing recursive templates.

What are fold expressions?

C++17 fold expressions “fold” a variadic template parameter pack with a single operator, without writing recursive templates. After you know template basics, folds make many variadic utilities shorter.

// Before C++17: recursion
template<typename T>
T sum(T value) {
    return value;
}

template<typename T, typename... Args>
T sum(T first, Args... args) {
    return first + sum(args...);
}

// C++17: fold expression
template<typename... Args>
auto sum(Args... args) {
    return (... + args);  // unary left fold
}

cout << sum(1, 2, 3, 4, 5) << endl;  // 15

The four fold forms

// 1. Unary right fold: (args op ...)
template<typename... Args>
auto sum1(Args... args) {
    return (args + ...);  // ((1 + 2) + 3) + 4
}

// 2. Unary left fold: (... op args)
template<typename... Args>
auto sum2(Args... args) {
    return (... + args);  // 1 + (2 + (3 + 4))
}

// 3. Binary right fold: (args op ... op init)
template<typename... Args>
auto sum3(Args... args) {
    return (args + ... + 0);  // ((1 + 2) + 3) + 0
}

// 4. Binary left fold: (init op ... op args)
template<typename... Args>
auto sum4(Args... args) {
    return (0 + ... + args);  // 0 + (1 + (2 + 3))
}

Unary vs binary folds

  • Unary fold: The pack appears on one side of the operator only. For an empty pack, only certain operators are allowed (see below); others are ill-formed.
  • Binary fold: (pack op ... op init) or (init op ... op pack) supplies an initializer, so an empty pack can yield init—handy for sums, products, and logical folds.

Same + operator: (0 + ... + args) starts from 0; (... + args) folds only the arguments. Associativity and empty-pack behavior differ.

Left vs right fold

  • Left fold (... op args): grouping associates from the left (exact tree follows the standard’s expansion rules).
  • Right fold (args op ...): grouping associates from the right.

For associative operations (+, *), order often does not matter for the value; for subtraction, division, or stream insertion, you must pick the fold that matches the intended order (e.g. (cout << ... << args) for natural print order).

Practical use in variadic functions

Typical patterns: (1) apply one operation to every argument; (2) split “first vs rest.” Folds excel at (1).

template<typename... Args>
void invoke_all(Args&&... f) {
    (std::forward<Args>(f)(), ...);
}

If zero arguments are possible, prefer a binary fold with an initializer or if constexpr (sizeof...(Args) == 0).

Common patterns: sum, print, all_of

GoalIdiomatic foldNotes
Sum(0 + ... + args) or (... + args)Use binary fold if empty is allowed
Product(1 * ... * args)Same idea
All true(... && args)Empty pack ⇒ true (unary && rules)
Any true(false || ... || args) or (args || ...)Empty unary ||false
Print(std::cout << ... << args)Mind stream types and order
push_back(vec.push_back(args), ...)Comma fold

Logical OR can use a binary fold with a false initializer or a unary right fold; for “all arguments are true,” (... && args) is often the clearest.

Role in the language

Fold expressions are a pure compile-time syntactic extension in C++17. There is no runtime cost for the fold itself; generated code is usually a flat sequence of operations. With C++20 concepts, you can constrain which types may appear in a fold.

Supported operators

Arithmetic, logical, bitwise, comparison, and comma operators are supported as specified in the standard—see cppreference for the full list.

Examples

Print

template<typename... Args>
void print(Args... args) {
    (cout << ... << args) << endl;
}

print("x = ", 42, ", y = ", 3.14);

All true

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

Push into a vector

template<typename T, typename... Args>
void push_back_all(vector<T>& vec, Args... args) {
    (vec.push_back(args), ...);
}

Call functions

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

Range / membership checks

Illustrative patterns—adapt to your types.

Min/max

Using std::min with an initializer list is often clearer than misusing < as a fold:

template<typename... Args>
auto minimum(Args... args) {
    return std::min({args...});
}

Comma operator

template<typename... Args>
void process2(Args... args) {
    ((cout << args << " "), ...);
}

Common pitfalls

Empty packs

template<typename... Args>
auto sum(Args... args) {
    return (0 + ... + args);  // OK: binary fold
}

// Empty-pack friendly unary folds: &&, ||, ,

Precedence

template<typename... Args>
auto func(Args... args) {
    return (... + (args * 2));  // parenthesize
}

Type consistency

template<typename T, typename... Args>
T sum(Args... args) {
    return (T(0) + ... + T(args));
}

Fold vs recursion

Folds replace long recursive templates for many “reduce over pack” patterns; recursion is still useful when you need non-uniform structure.

FAQ

Q1: When should I use folds?
A: Variadic templates, pack processing, and anywhere you would otherwise write recursion.

Q2: Are all operators supported?
A: Binary operators yes; unary operators cannot be folded in the same way.

Q3: Performance?
A: Similar to an equivalent expanded loop; compile time may improve vs deep recursion.

Q4: Empty packs?
A: Only &&, ||, , unary folds allow an empty pack; otherwise use a binary fold or a branch.

Q5: Before C++17?
A: Use recursive templates or helper metafunctions.

Q6: Resources?
A: C++17 — The Complete Guide, cppreference, Effective Modern C++.

Related: Advanced variadic templates, template basics, template argument deduction.


  • Advanced variadic templates
  • Template basics
  • Template argument deduction

Keywords

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

See also

  • Variadic templates
  • auto deduction
  • CTAD
  • C++20 Concepts
  • constexpr if
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3