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).opis any of the 32 fold-compatible operators in C++ (such as+,-,*,/,<<,,, etc.).packis 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:
- Logical AND (
&&): Evaluates totrueif the pack is empty. - Logical OR (
||): Evaluates tofalseif the pack is empty. - Comma Operator (
,): Evaluates tovoid()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.