C++ Fold Expressions | Folding Parameter Packs in C++17
이 글의 핵심
C++17 fold expressions explained: unary/binary folds, left vs right, empty packs, supported operators, and patterns for sum, print, all_of, and containers.
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 yieldinit—handy for sums, products, and logical folds. Same+operator:(0 + ....+ args)starts from0;(....+ 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
| Goal | Idiomatic fold | Notes |
|---|---|---|
| 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 |
(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
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.
Related posts
Keywords
C++, fold expression, variadic, parameter pack, C++17.
See also
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 가변 인자 템플릿 고급 | pack 확장과 fold 표현식
- C++ 가변 인자 템플릿 | ‘가변 템플릿’ 완벽 가이드
- C++ Fold Expressions | ‘Parameter Pack Folding’ Guide
이 글에서 다루는 키워드 (관련 검색어)
C++, Fold Expression, Variadic, Templates, C++17 등으로 검색하시면 이 글이 도움이 됩니다.