Advanced C++ Variadic Templates | Pack Expansion and Fold Expressions
이 글의 핵심
Advanced variadic templates: pack expansion, fold expressions, and patterns you will use in real code.
Advanced variadic templates
A variadic template accepts a variable number of types or values. You pack them with ...Args, then unpack with recursion or fold expressions (C++17). Pair this with template basics and template specialization for stronger designs.
Packing and recursion
To accept variadic arguments, declare a parameter pack and split work between a base case (zero arguments) and a recursive case (first argument + rest of the pack).
#include <iostream>
// Base case
void print() {
std::cout << '\n';
}
// Recursive case
template<typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << ' ';
print(rest...); // expand rest and recurse
}
int main() {
print(1, 2.0, "three");
// output: 1 2 three
return 0;
}
How it works:
print(1, 2.0, "three"):T = int,Rest = {double, const char*}- Print
1, thenprint(2.0, "three") - Continue until
Restis empty, thenprint()(base)
Tip: Rest... is “the remaining types”; rest... expands the remaining arguments for the next print call. A fold expression can replace this recursion in one line.
template<typename... Args>
void print_fold(Args... args) {
(std::cout << ... << args) << '\n';
}
print_fold(1, 2.0, "three"); // no explicit recursion
Fold expressions (C++17)
A fold applies a binary operator across the whole pack. (args + ...) is a unary right fold, equivalent to args_1 + (args_2 + (args_3 + ...)).
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // unary right fold
}
template<typename... Args>
bool all(Args... args) {
return (args && ...);
}
// Empty pack: use a binary fold with an initializer
template<typename... Args>
auto sum_with_zero(Args... args) {
return (0 + ... + args); // empty pack => 0
}
Use cases include logging, small utility wrappers, and type-list processing.
Pack expansion patterns
Expanding a pack as (expr)... repeats expr for each element.
#include <iostream>
#include <vector>
template<typename... Args>
void call_functions(Args... args) {
(process(args), ...); // process(arg1), process(arg2), ...
}
template<typename... Args>
void add_to_vector(std::vector<int>& v, Args... args) {
(v.push_back(args), ...);
}
template<typename... Args>
void log(Args&&... args) {
(std::cout << ... << args) << '\n';
}
Expansion patterns:
| Pattern | Meaning | Example |
|---|---|---|
args... | Unpack pack | f(args...) → f(a1, a2, a3) |
(expr(args))... | Per-element expression | (f(args))... |
(args + ...) | Unary right fold | |
(... + args) | Unary left fold | |
(init + ... + args) | Binary fold |
Examples:
template<typename... Args>
void set_values(int& result, Args... args) {
result = (args + ...);
}
template<typename... Args>
bool all_true(Args... args) {
return (args && ...);
}
template<typename... Funcs>
void execute_all(Funcs... funcs) {
(funcs(), ...);
}
Example: type-safe tuple indexing idea
You can build a metafunction that returns the type at index I from a type list—useful with structured bindings.
template<typename... Ts>
struct type_list {};
template<size_t I, typename T>
struct type_at_impl;
template<typename T, typename... Rest>
struct type_at_impl<0, type_list<T, Rest...>> {
using type = T;
};
template<size_t I, typename T, typename... Rest>
struct type_at_impl<I, type_list<T, Rest...>> {
using type = typename type_at_impl<I - 1, type_list<Rest...>>::type;
};
Related reading
- Template template parameters
autotype deduction
Common issues
- Empty packs: Some operators are ill-formed on empty unary folds; use a binary fold with an initializer or
if constexpr (sizeof...(args) > 0). - Expansion context:
(args)...vs(args...)differ—parentheses group what repeats.
Summary
| Topic | Notes |
|---|---|
| Parameter pack | typename... Args, Args... args |
| Fold | (args op ...), (... op args), (init op ... op args) |
| Practice | Logging, wrappers, type lists, variadic adapters |
In one sentence: Variadic templates let you write APIs independent of arity; C++17 folds often replace recursion.
Related posts
- Variadic templates series
- Fold expressions
- Template basics
- Template specialization
- Template template parameters
Keywords
C++, variadic template, parameter pack, fold expression, C++17.
Checklist
Before coding
- Is this the best approach?
- Can the team maintain it?
- Does it meet performance goals?
While coding
- Warnings addressed?
- Edge cases covered?
- Errors handled?
At review
- Clear intent?
- Enough tests?
- Documented?
FAQ
Q. Where is this used in production?
A. Logging, generic adapters, type lists, and any API that must accept a variable number of types or values—see the patterns above.
Q. What should I read first?
A. Follow Related posts at the bottom of each article, or use the C++ series index.
Q. Go deeper?
A. cppreference and library docs are the authoritative references.