Unary Right Fold Expressions in C++ Explained
Introduced in C++17, fold expressions provide a concise syntax for reducing variadic template parameter packs over binary operators. A unary right fold evaluates an unpacked sequence from right to left without requiring an explicit initial accumulator value. This article explains the syntax, substitution mechanics, associativity behavior, and edge cases associated with unary right folds in C++.
Syntax and Expansion Pattern
A unary right fold uses the parameter pack on the left of the ellipsis and the operator. The formal syntax is:
( pack op ... )Here, pack represents an unexpanded parameter pack, and
op represents any valid fold-compatible binary operator
(such as +, -, *,
&&, etc.). The surrounding parentheses are
mandatory.
When a parameter pack containing elements \(E_1, E_2, \dots, E_N\) is expanded via a unary right fold, the compiler replaces the expression with a right-nested tree of operations:
(E1 op (E2 op (... (E_{N-1} op E_N))))Evaluation Order and Associativity
Because the grouping nests from the right inward, the innermost expression on the far right evaluates first, propagating results back toward the leftmost operand.
Consider subtraction over the sequence of integers
10, 5, 2:
template <typename... Args>
auto right_subtract(Args... args) {
return (args - ...);
}
int result = right_subtract(10, 5, 2);The expression (args - ...) expands directly to:
10 - (5 - 2)The rightmost sub-expression (5 - 2) evaluates to
3, resulting in 10 - 3 = 7. This contrasts
with a unary left fold ((... - args)), which expands to
((10 - 5) - 2) = 3. For non-associative operators such as
subtraction or division, selecting between a right fold and a left fold
fundamentally alters the computed result.
Member Access and Right-Associative Operators
Unary right folds are particularly useful when working with operators
that naturally associate from right to left, such as assignment
(=) or pointer-to-member operations (.*,
->*).
For example, chaining assignment operations across multiple variables preserves standard right-to-left assignment semantics:
template <typename... Args>
void assign_all(Args&... args) {
(args = ...); // Expands to: (arg1 = (arg2 = (arg3 = ...)))
}Empty Parameter Pack Rules
In general, unary fold expressions over empty parameter packs are ill-formed and cause a compilation error. However, the C++ standard defines three exceptions for unary folds with empty packs:
- Logical AND (
&&): Expands totrue. - Logical OR (
||): Expands tofalse. - Comma Operator (
,): Expands tovoid().
If an empty pack is passed to a unary right fold using any operator
other than &&, ||, or ,,
the code fails to compile. To handle potentially empty parameter packs
with operators like + or *, a binary fold
containing an explicit identity element should be used instead.