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:

  1. Logical AND (&&): Expands to true.
  2. Logical OR (||): Expands to false.
  3. Comma Operator (,): Expands to void().

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.