Přeskočit na obsah

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.

recursive_variadic.cpp
#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

fold_expressions.cpp
#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.

tuple_apply.cpp
#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)
}
Úskalí

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.

Osvědčený postup

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.

Klíčové poznatky
  • Variadic templates usetypename... 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)`?