функции как значения · обобщённый кодfunctions as values · generic code
Шаблоны с произвольным, неограниченным числом параметров типов. Фундамент, на котором построены make_unique, std::tuple, std::function и perfect forwarding для функций с любым числом аргументов.Templates with an arbitrary, unbounded number of type parameters. The foundation underlying make_unique, std::tuple, std::function, and perfect forwarding for functions with any number of arguments.
template<class... Args> — параметр-пакет (parameter pack), представляющий ноль или более типов сразу. Функция или класс с таким шаблоном может принимать произвольное число аргументов произвольных типов, а пакет разворачивается через синтаксис Args... в местах, где нужен список типов или значений.template<class... Args> — a parameter pack, representing zero or more types at once. A function or class built on such a template can accept an arbitrary number of arguments of arbitrary types, and the pack is expanded via the Args... syntax wherever a list of types or values is needed.
void print(); // база рекурсии// recursion base case
template <class T>
void print(T a) { std::cout << a; }
template <class T1, class T2>
void print(T1 a, T2 b) { print(a); print(b); }
// ...и так для каждого числа аргументов// ...and so on for every argument count
void print() {} // база рекурсии// recursion base case
template <class T, class... Rest>
void print(T first, Rest... rest) {
std::cout << first;
print(rest...); // рекурсивное разворачивание// recursive unpacking
}
До C++11 переменное число аргументов в шаблонном коде решалось плохо:Before C++11, variable argument counts in template code were handled poorly:
printf-подобные функции через va_list не проверяют типы аргументов на этапе компиляции — несоответствие формата и типа приводит к undefined behavior в рантайме, что было источником уязвимостей.printf-style functions built on va_list don't check argument types at compile time — a mismatch between format and type causes undefined behavior at runtime, which was a well-known source of vulnerabilities.
Библиотеки вроде Boost.Tuple до C++11 генерировали перегрузки для 1, 2, 3... N аргументов через макросы препроцессора (Boost.Preprocessor) — рабочее, но громоздкое и трудно читаемое решение с искусственным потолком N.Libraries like Boost.Tuple, before C++11, generated overloads for 1, 2, 3... N arguments via preprocessor macros (Boost.Preprocessor) — a working but bulky, hard-to-read solution with an artificial cap on N.
std::make_unique<T>(args...) должен принять любое число аргументов конструктора T и передать их дальше — без variadic templates эта задача нерешаема в общем виде.std::make_unique<T>(args...) must accept any number of constructor arguments for T and forward them onward — without variadic templates this problem has no general solution.
Компилятор инстанцирует отдельную функцию (или класс) для каждого конкретного набора типов, с которым вызван шаблон — сам параметр-пакет не существует в рантайме, это чисто механизм этапа компиляции. Разворачивание пакета (...) — операция, которую компилятор применяет к паттерну, содержащему пакет, порождая список через запятую или рекурсивный вызов.The compiler instantiates a separate function (or class) for each concrete set of types the template is invoked with — the parameter pack itself doesn't exist at runtime, it's a purely compile-time mechanism. Pack expansion (...) is an operation the compiler applies to a pattern containing the pack, producing a comma-separated list or a recursive call.
template <class... Args>
void log(Args&&... args) { // args — параметр-пакет// args is a parameter pack
(std::cout << ... << args); // C++17 fold expression, для контраста;// C++17 fold expression, shown for contrast;
} // в C++11 — только явная рекурсия (см. print выше)// in C++11 — only explicit recursion (see print above)
template <class T, class... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
// Args&&... args — пакет universal references,// Args&&... args — a pack of universal references,
// std::forward<Args>(args)... — разворачивание с forward на каждый элемент// std::forward<Args>(args)... — expansion with forward applied to each element
}
// sizeof...(Args) — число элементов в пакете, вычисляется на этапе компиляции// sizeof...(Args) — the number of elements in the pack, computed at compile time
template <class... Args>
constexpr std::size_t count() { return sizeof...(Args); }
В C++11 единственный способ обработать пакет поэлементно — рекурсия с базовым случаем (перегрузка без пакета или с одним элементом); fold expressions ((... op args)), которые убирают эту рекурсию, — фича C++17.In C++11, the only way to process a pack element by element is recursion with a base case (an overload with no pack, or with a single element); fold expressions ((... op args)), which eliminate that recursion, are a C++17 feature.
Без нерекурсивной перегрузки print() без аргументов компилятор либо не сможет остановить инстанцирование при пустом пакете, либо выдаст ошибку об отсутствии подходящей перегрузки на последнем шаге разворачивания.Without a non-recursive print() overload taking no arguments, the compiler either can't stop instantiating on an empty pack, or errors out with no matching overload at the last step of expansion.
При разворачивании пакета в списке аргументов вызова (f(g(args)...)) порядок вычисления каждого g(args) не специфицирован стандартом C++11 — если g имеет побочные эффекты, порядок их проявления не гарантирован.When a pack is expanded inside a call's argument list (f(g(args)...)), the evaluation order of each g(args) is unspecified by the C++11 standard — if g has side effects, the order in which they occur is not guaranteed.
sizeof...(Args) возвращает количество типов в пакете, а не размер в байтах — синтаксически похоже на sizeof одного типа, что путает при беглом чтении незнакомого кода.sizeof...(Args) returns the number of types in the pack, not a size in bytes — it looks syntactically similar to sizeof of a single type, which is confusing on a quick read of unfamiliar code.