C++ Variadic Templates: Unary Left Fold Syntax

Fold expressions, introduced in C++17, provide a concise mechanism to unpack variadic template arguments across binary operators. This article explains the exact syntax, expansion rules, and behavioral semantics of unary left folds in modern C++, detailing how they are structured, evaluated, and handled when operating on parameter packs.

Syntax of a Unary Left Fold

In C++, a unary left fold expression consists of an ellipsis (...), a binary operator, and an unexpanded parameter pack, all enclosed within parentheses:

(... op pack)

The outer parentheses are syntactically mandatory. In this expression:

  • ... represents the fold direction (left-to-right).
  • op is any of the 32 fold-compatible operators in C++ (such as +, -, *, /, <<, ,, etc.).
  • pack is the unexpanded parameter pack.

Evaluation and Expansion Order

A unary left fold groups operations from left to right. When instantiated with a pack of \(N\) arguments (E1, E2, ..., EN), the expression expands into:

(((E1 op E2) op E3) op ...) op EN

The leftmost elements are grouped and evaluated first. This distinction is critical for non-associative operators such as subtraction (-) or division (/), as well as stream insertion operators (<<).

Example: Output Stream

#include <iostream>

template <typename... Args>
void printAll(Args&&... args) {
    // Unary left fold using operator<<
    (std::cout << ... << args) << '\n'; // Note: this specific construct is typically written as a binary fold, 
                                        // but illustrates left-chaining.
}

template <typename... Args>
auto subtractAll(Args... args) {
    // Unary left fold with operator-
    return (... - args);
}

int main() {
    // Evaluates as: (((10 - 3) - 2) - 1) = 4
    int result = subtractAll(10, 3, 2, 1);
    std::cout << result; // Prints 4
}

In subtractAll(10, 3, 2, 1), the unary left fold (... - args) expands strictly as:

(((10 - 3) - 2) - 1)

If this were a unary right fold ((args - ...)) instead, it would expand to (10 - (3 - (2 - 1))), yielding 8.

Behavior with Empty Parameter Packs

Unary folds typically require at least one element. If the pack is empty, an instantiation of a unary left fold is ill-formed, with three standardized exceptions:

  1. Logical AND (&&): Evaluates to true if the pack is empty.
  2. Logical OR (||): Evaluates to false if the pack is empty.
  3. Comma Operator (,): Evaluates to void() if the pack is empty.

For any other operator (such as +, -, or *), passing an empty parameter pack to a unary left fold will result in a compile-time error. When empty packs must be supported for other operators, a binary fold with an explicit fallback value should be used instead.