C++SeniorCoding
Что такое variadic templates?
Variadic templates (C++11) принимают произвольное число типовых аргументов через parameter packs (typename... Args). До C++17 раскрываются рекурсией; в C++17 fold expressions (args + ...) делают это однострочно без рекурсии.
Что такое variadic templates
Variadic templates (C++11) — шаблоны с переменным числом типовых параметров (parameter packs). Позволяют писать функции и классы, принимающие произвольное количество аргументов любых типов, при полной проверке типов на этапе компиляции.
#include <iostream>
#include <string>
#include <tuple>
#include <memory>
// sizeof... — количество элементов в пакете
template<typename... Args>
void count_args(Args... args) {
std::cout << "count: " << sizeof...(args) << '\n';
}
// Рекурсивное раскрытие (базовый случай + рекурсия)
void print() {} // базовый случай: ноль аргументов
template<typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << ' ';
print(rest...); // рекурсия с оставшимися аргументами
}
// C++17 fold expressions — намного лаконичнее рекурсии
template<typename... Args>
void print_fold(Args... args) {
// унарный правый fold: (args << ... ) — нет; используем запятую
((std::cout << args << ' '), ...);
std::cout << '\n';
}
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // fold expression: a + b + c + ...
}
template<typename... Args>
bool all_positive(Args... args) {
return ((args > 0) && ...); // all-of fold
}
// Практический пример: аналог std::make_unique
template<typename T, typename... Args>
std::unique_ptr<T> my_make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
// Вариадический класс: std::tuple реализован так
template<typename... Types>
struct MyTuple; // forward declaration
template<>
struct MyTuple<> {}; // base case
template<typename Head, typename... Tail>
struct MyTuple<Head, Tail...> : MyTuple<Tail...> {
Head value;
explicit MyTuple(Head h, Tail... t)
: MyTuple<Tail...>(t...), value(h) {}
};
// Logging с типами (реальный use-case)
template<typename... Args>
void log(const char* fmt, Args&&... args) {
// В реальном коде: fmt::format или std::format (C++20)
// Здесь упрощённо:
((std::cout << std::forward<Args>(args) << ' '), ...);
std::cout << '\n';
}
int main() {
count_args(1, "hello", 3.14); // count: 3
print(1, 2.5, "three"); // 1 2.5 three
print_fold(10, 20, 30); // 10 20 30
std::cout << sum(1, 2, 3, 4, 5) << '\n'; // 15
std::cout << all_positive(1, 2, 3) << '\n'; // 1
std::cout << all_positive(1, -2, 3) << '\n'; // 0
struct Point { int x, y; };
auto p = my_make_unique<Point>(1, 2);
log("Values:", 42, "text", 3.14f);
}
Fold expressions (C++17)
Четыре вида fold:
- Унарный левый:
(... op pack)→((a op b) op c) - Унарный правый:
(pack op ...)→(a op (b op c)) - Бинарный левый:
(init op ... op pack) - Бинарный правый:
(pack op ... op init)
Подводные камни
- Глубокая рекурсия без fold: до C++17 рекурсивные variadic шаблоны генерируют глубокую цепочку инстанциаций — медленная компиляция и возможное превышение лимита глубины шаблонов (обычно 900).
- Порядок вычислений в fold: для operator
,порядок гарантирован слева направо, для+— нет (применяйте бинарный fold с осторожностью для операций с побочными эффектами). - Пустой пакет и fold: унарный fold с пустым пакетом допустим только для
&&(→ true),||(→ false) и,(→ void); для+с пустым пакетом — ошибка компиляции. - Раскрытие в инициализаторе:
int arr[] = {(expr(args), 0)...};— старый трюк для гарантии порядка; с C++17 используйте fold с запятой. - SFINAE и variadic: ошибки внутри инстанциации пакета дают трудночитаемые сообщения; в C++20 используйте concepts и
requires. - Перегрузка vs специализация: нельзя частично специализировать variadic функцию — используйте перегрузку или вспомогательный шаблон класса.
- Производительность компиляции: вариадические шаблоны с большим числом аргументов (100+) резко замедляют компиляцию; для таких случаев рассмотрите type erasure или
std::initializer_list.
Common mistakes
- Объяснять variadic templates только по синтаксису, без жизненного цикла и стоимости.
- Игнорировать ошибки, null/empty состояния, порядок инициализации или режим сборки.
- Давать пример, который работает в демо, но ломается при изменении владельца ресурса.
- Показывать сырой указатель без объяснения владельца и момента освобождения.
What the interviewer is testing
- Кандидат формулирует точную модель для variadic templates, а не только определение.
- Пример компилируем, безопасен по lifetime и соответствует версии технологии.
- Названы trade-off, ограничения и диагностируемые симптомы ошибки.