Skip to main content

Fold Expressions (C++17)

Fold expressions provide a concise way to apply operators to variadic template parameter packs. They eliminate the need for recursive template patterns.

Simplifying Variadic Templates

Before C++17: Complex recursion
C++17: (args + ...) - simple and readable!

Basic Fold Expressions

// Sum all arguments
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // Fold with +
}

sum(1, 2, 3, 4, 5); // 15

// Multiply all arguments
template<typename... Args>
auto product(Args... args) {
return (args * ...); // Fold with *
}

product(2, 3, 4); // 24

Syntax: (pack op ...)

Four Types of Folds

Unary right fold: (pack op ...)

(args + ...)  // ((arg1 + arg2) + arg3) + ...

Unary left fold: (... op pack)

(... + args)  // ... + (arg3 + (arg2 + arg1))

Binary right fold: (pack op ... op init)

(args + ... + 0)  // ((arg1 + arg2) + arg3) + 0

Binary left fold: (init op ... op pack)

(0 + ... + args)  // 0 + (arg1 + (arg2 + arg3))

Common Operations

Logical AND:

template<typename... Args>
bool all(Args... args) {
return (args && ...); // All must be true
}

all(true, true, true); // true
all(true, false, true); // false

Logical OR:

template<typename... Args>
bool any(Args... args) {
return (args || ...); // At least one true
}

any(false, false, true); // true
any(false, false, false); // false

Comma operator (execute all):

template<typename... Args>
void print(Args... args) {
((std::cout << args << " "), ...);
std::cout << "\n";
}

print(1, "hello", 3.14); // "1 hello 3.14"

With Initial Value

// Sum with initial value
template<typename... Args>
auto sumFrom(int start, Args... args) {
return (start + ... + args); // start + arg1 + arg2 + ...
}

sumFrom(100, 1, 2, 3); // 106

// Concatenate strings
template<typename... Args>
std::string concat(Args... args) {
return (std::string("") + ... + args);
}

concat("Hello", " ", "World"); // "Hello World"

Function Calls

// Call function for each argument
template<typename... Args>
void processAll(Args... args) {
(process(args), ...);
}

// Push all to vector
template<typename... Args>
void addAll(std::vector<int>& vec, Args... args) {
(vec.push_back(args), ...);
}

std::vector<int> v;
addAll(v, 1, 2, 3, 4, 5); // v = {1, 2, 3, 4, 5}

Comparison Chains

// Check if all equal
template<typename T, typename... Args>
bool allEqual(T first, Args... args) {
return ((first == args) && ...);
}

allEqual(5, 5, 5, 5); // true
allEqual(5, 5, 6, 5); // false

// Check if in ascending order
template<typename... Args>
bool isAscending(Args... args) {
return ((args < args...) && ...); // Won't work directly!
}

Combining Folds

// Average
template<typename... Args>
double average(Args... args) {
return (args + ...) / sizeof...(args);
}

average(10, 20, 30); // 20.0

// All within range
template<typename... Args>
bool allInRange(int min, int max, Args... args) {
return ((args >= min && args <= max) && ...);
}

allInRange(0, 100, 50, 75, 25); // true
allInRange(0, 100, 50, 150, 25); // false

With Member Functions

struct Point {
int x, y;
void print() const {
std::cout << "(" << x << "," << y << ") ";
}
};

template<typename... Args>
void printAll(const Args&... args) {
(args.print(), ...);
}

Point p1{1, 2}, p2{3, 4}, p3{5, 6};
printAll(p1, p2, p3); // "(1,2) (3,4) (5,6)"

Building Tuples

template<typename... Args>
auto makeTuple(Args... args) {
return std::tuple<Args...>(args...);
}

// Unpacking with fold
template<typename Tuple, size_t... Is>
void printTuple(const Tuple& t, std::index_sequence<Is...>) {
((std::cout << std::get<Is>(t) << " "), ...);
}

auto t = makeTuple(1, "hello", 3.14);
printTuple(t, std::make_index_sequence<3>{}); // "1 hello 3.14"

Comparison: Before and After

Before C++17 (recursive):

// Base case
void print() {}

// Recursive case
template<typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...);
}

C++17 (fold expression):

template<typename... Args>
void print(Args... args) {
((std::cout << args << " "), ...);
}

Much simpler and clearer!

Operator Support

Fold expressions work with these operators:

  • Arithmetic: +, -, *, /, %
  • Bitwise: &, |, ^, <<, >>
  • Logical: &&, ||
  • Comparison: <, >, <=, >=, ==, !=
  • Assignment: =, +=, -=, etc.
  • Comma: ,
  • Member access: .*, ->*
Fold Expression Patterns

Sum = (args + ...)
Product = (args * ...)
All true = (args && ...)
Any true = (args || ...)
Call function = (func(args), ...)
Print all = ((cout << args << " "), ...)
With initial = (init + ... + args)
Pack size = sizeof...(args) (not a fold, but related)