Вариативные шаблоны
Шаблоны, принимающие любое число аргументов любых типов
std::make_unique, vector::emplace_back, std::tuple и типобезопасное форматирование. Всё разрешается компилятором на этапе инстанцирования, поэтому вариативный вызов стоит ровно столько же, сколько написанная вручную функция, в которую он разворачивается: без varargs, без стирания типов во время выполнения, без кучи.
- объявлять шаблонные и функциональные пакеты параметров и узнавать их длину через
sizeof..., - разворачивать пакет оператором
..., включая разворачивание шаблона выражения, - обрабатывать пакет рекурсией (голова / хвост) и понимать, когда нужен базовый случай,
- сворачивать пакет в одно выражение через fold-выражения C++17,
- идеально пробрасывать пакет через
std::forward.
1. Пакеты параметров и sizeof...
Вместе работают два вида пакетов. Шаблонный пакет параметров записывается с ... после typename (или после нетипового либо шаблонного параметра); функциональный пакет параметров использует этот пакет как тип для последовательности параметров функции.
template <typename... Ts> // Ts - ШАБЛОННЫЙ пакет параметров (список типов)
void probe(Ts... args) { // args - ФУНКЦИОНАЛЬНЫЙ пакет параметров (список значений)
static_assert(sizeof...(Ts) == sizeof...(args)); // обе длины совпадают
}
Пакет - это не тип и не значение. Это список времени компиляции, с которым можно сделать ровно две вещи: узнать его длину или развернуть его. Длину даёт sizeof... - это constexpr типа std::size_t, вычисляемый целиком на этапе компиляции; он считает элементы, а не суммирует их размеры:
template <typename... Ts>
void howMany(Ts... args) {
std::cout << "arity = " << sizeof...(args) << "\n"; // например 3
}
howMany(1, 2.0, 'x'); // arity = 3 ; Ts = {int, double, char}
Пустой пакет допустим: howMany() компилируется и печатает arity = 0. Именно этот граничный случай - источник большинства ошибок в вариативном коде, поэтому держите его в голове с самого начала.