Variadic Templates & Fold Expressions
Handle any number of template arguments with parameter packs. Master fold expressions for elegant, recursive-free pack processing.
Balíčky parametrů
A variadic template accepts an arbitrary number of template arguments using a parameter pack. The syntax uses an ellipsis (...). There are two kinds of packs:
1. Template parameter pack —template — captures zero or more type arguments.
2. Function parameter pack —void f(Ts... args)— captures the corresponding function arguments.
You cannot directly iterate over a pack in a loop. Instead, you expand it using the...operator. The key insight is that pack expansion applies a pattern to every element of the pack and produces a comma-separated list.
sizeof...(Ts)andsizeof...(args)return the number of elements in the pack as aconstexpr std::size_t.
Rekurzivní rozšíření balíčku (starší vzor)
Před skládáním výrazů C++17 byla standardním způsobem zpracování balíčků rekurzivní konkretizace: odloupněte první prvek, zpracujte jej a znovu použijte zbytek. To vyžaduje základní případ pro ukončení rekurze.
#include <iostream>
// Base case: no arguments
void print() {
std::cout << '\n';
}
// Recursive case: process first, recurse on rest
template <typename First, typename... Rest>
void print(const First& first, const Rest&... rest) {
std::cout << first;
if constexpr (sizeof...(rest) > 0) {
std::cout << ", ";
}
print(rest...); // expand rest, recurse
}
int main() {
print(1, 2.5, "hello", 'A');
// Output: 1, 2.5, hello, A
print(); // just a newline
}Skládací výrazy (C++17)
C++17 výrazy skládání ve většině případů eliminují potřebu rekurzivních šablon. Výraz fold aplikuje binární operátor na všechny prvky sady parametrů. Existují čtyři formy:
- Unární pravý fold:(pack op ...)se rozšíří nae1 op (e2 op (... op eN))
- Unární levý sklad:(... op pack)se rozšíří na((e1 op e2) op ...) op eN
- Binární pravý fold:(pack op ... op init)— obsahuje počáteční hodnotu
- Binární levý fold:(init op ... op pack)— obsahuje počáteční hodnotu
#include <iostream>
#include <string>
// Sum all arguments — unary left fold with +
template <typename... Ts>
auto sum(Ts... args) {
return (... + args); // ((a1 + a2) + a3) + ...
}
// Print all arguments — binary left fold with <<
template <typename... Ts>
void print(Ts&&... args) {
(std::cout << ... << args) << '\n';
// expands to: ((std::cout << a1) << a2) << a3 ...
}
// Check if ALL arguments satisfy a predicate — unary left fold with &&
template <typename... Ts>
bool all_positive(Ts... args) {
return (... && (args > 0)); // (a1>0) && (a2>0) && ...
}
// Push multiple elements to a vector — fold over comma operator
#include <vector>
template <typename T, typename... Ts>
void push_all(std::vector<T>& vec, Ts&&... args) {
(vec.push_back(std::forward<Ts>(args)), ...);
}
int main() {
std::cout << sum(1, 2, 3, 4, 5) << '\n'; // 15
print("x=", 10, " y=", 20); // x=10 y=20
std::cout << std::boolalpha
<< all_positive(1, 2, 3) << '\n' // true
<< all_positive(1, -2, 3) << '\n'; // false
std::vector<int> v;
push_all(v, 10, 20, 30);
for (int x : v) std::cout << x << ' '; // 10 20 30
std::cout << '\n';
}Kontexty rozšíření balíčku
Rozšíření balíčku (...) se může objevit v mnoha kontextech, nejen jako argumenty volání funkce:
- Funkční argumenty:f(args...)nebof(transform(args)...)
- Argumenty šablony:std::tuple
- Základní třídy:class Derived : public Bases... { };
- Seznamy inicializátorů:auto list = { args... };
- Lambda capture:[args...] { }nebo[...args = std::move(args)] { }(C++20 init-capture)
- Použití deklarací (C++17):using Bases::operator()...;
vzor je to, co se objeví před.... Vf(transform(args)...)je vzortransform(args), který se rozšiřuje naf(transform(a1), transform(a2), transform(a3)).
To je důležité pro pochopení:f(args)...není totéž jakof(args...). První se rozšíří naf(a1), f(a2), f(a3)(tři samostatná volání ve výrazu s čárkou), zatímco druhá se rozšíří naf(a1, a2, a3)(jedno volání se třemi argumenty).
std::tuple & std::použít
std::tupleje heterogenní kontejner standardní knihovny, postavený na variadických šablonách. std::apply (C++17) volá callable s n-ticovými prvky rozbalenými jako argumenty. std::index_sequence umožňuje indexování do n-tic v době kompilace.
#include <tuple>
#include <iostream>
#include <string>
#include <utility>
// std::apply calls a function with tuple elements as arguments
void greet(const std::string& name, int age) {
std::cout << "Hello " << name << ", age " << age << '\n';
}
// Print all tuple elements using index_sequence
template <typename Tuple, std::size_t... Is>
void print_tuple_impl(const Tuple& t, std::index_sequence<Is...>) {
((std::cout << (Is == 0 ? "" : ", ") << std::get<Is>(t)), ...);
}
template <typename... Ts>
void print_tuple(const std::tuple<Ts...>& t) {
std::cout << '(';
print_tuple_impl(t, std::index_sequence_for<Ts...>{});
std::cout << ")\n";
}
int main() {
auto person = std::make_tuple(std::string("Alice"), 30);
std::apply(greet, person); // Hello Alice, age 30
auto data = std::make_tuple(1, 3.14, std::string("hello"));
print_tuple(data); // (1, 3.14, hello)
}Nejčastější chyby s variadickými šablonami:
1. Zapomenutí... — zápisargs, když máte na mysliargs..., ponechá balíček nerozbalený, což způsobí chybu kompilace.
2. Rozšiřování na nesprávném místě —f(g(args)...)vsf(g(args...))mají velmi odlišné významy. První volágjednou na prvek; druhý volágse všemi prvky.
3. Prázdné balíčky —(... + args)s prázdným balíčkem je pro většinu operátorů špatně tvarovaný. Použijte binární sklad s počáteční hodnotou:(0 + ... + args).
4. sizeof...vrací počet, nikoli velikost v bajtech –sizeof...(args)udává počet prvků balíčku, nikoli jejich kombinovanou velikost paměti.
Kdykoli je to možné, upřednostňujte výrazy skládání před rekurzivními variadickými šablonami. Jsou stručnější, snáze se čtou a často se rychleji sestavují. Použijte binární sklady s explicitní počáteční hodnotou pro bezpečné zacházení s prázdnými balíčky. Rekurzivní expanzi si rezervujte pro případy, kdy jsou výrazy skládání nedostatečné – například když potřebujete zpracovat prvky ve dvojicích nebo když zpracování každého prvku vyžaduje jinou logiku.
- Variadic templates use
typename... Ts(template parameter pack) andTs... args(function parameter pack) sizeof...(args)returns the number of elements in a pack at compile time- Pack expansion applies a pattern to each element:
f(transform(args)...)calls transform on each element - Fold expressions (C++17) replace recursive expansion:
(... + args)sums all elements - Use binary folds with an initial value for safe handling of empty packs
std::applyunpacks a tuple as function arguments;std::index_sequenceenables compile-time indexing
Kvíz — Otestujte své znalosti
(20 XP)1. Na co se`(... + args)`rozšiřuje pro`args = {a, b, c}`?
2. Jaký je rozdíl mezi`f(args...)`a`f(args)...`?
3. Co vrací`sizeof...(args)`?