Вариативные шаблоны: первое знакомство
Функция double average(double a, double b) считает среднее двух чисел. Для трёх чисел понадобится ещё одна перегрузка, для пяти — ещё одна. Шаблон с многоточием после typename принимает любое число аргументов любых типов:
#include <iostream>
template <typename... Ts>
double average(Ts... xs) {
return (xs + ... + 0.0) / sizeof...(Ts);
}
int main() {
std::cout << average(1, 2) << ' ' << average(1, 2.5, 4) << '\n'; // 1.5 2.5
}Пакет параметров
Такой шаблон называют вариативным (variadic template). Запись typename... Ts объявляет пакет параметров (parameter pack) — не один тип, а список типов любой длины, в том числе пустой. В списке параметров функции Ts... xs — пакет значений, по одному на каждый тип из Ts. Для вызова average(1, 2.5, 4) компилятор выводит Ts как int, double, int, а xs содержит три значения.
Для каждого набора типов компилятор инстанцирует отдельную функцию: average(1, 2) и average(1, 2.5, 4) вызывают две разные функции. sizeof...(Ts) — число элементов пакета, а не размер в байтах, как у sizeof. Для average(1, 2.5, 4) оно равно 3, а у пустого пакета — 0. Это значение известно при компиляции.
Выражение свёртки
Перебрать пакет циклом нельзя: у его элементов могут быть разные типы. Вместо цикла пишут выражение свёртки (fold expression): пакет, оператор и многоточие в обязательных круглых скобках. Для трёх значений (xs + ... + 0.0) раскрывается в x1 + (x2 + (x3 + 0.0)), где x1, x2 и x3 — элементы xs. Последнее слагаемое 0.0 — начальное значение: для пустого пакета выражение равно ему.
Начальное значение влияет и на тип суммы. С 0 сумма двух int осталась бы int, и average(1, 2) вернула бы 1: деление целых чисел отбрасывает дробную часть. С 0.0 сумма имеет тип double, и результат равен 1.5.
Тип результата такой функции не всегда известен заранее. Тогда вместо него пишут auto, и компилятор выводит тип из return, как у лямбды: auto f() { return 1 + 2.5; } возвращает double.
Частая ошибка
Свёртку записали без начального значения:
#include <iostream>
template <typename... Ts>
double total(Ts... xs) {
return (xs + ...); // ✗
}
int main() {
std::cout << total(1, 2) << total() << '\n';
}error: unary fold expression has empty expansion for operator '+' with no fallback valueДля total(1, 2) такая свёртка работает, а у total() пакет пуст, и складывать нечего. Компилятор сообщает об ошибке, когда инстанцирует total(). Добавьте начальное значение: (xs + ... + 0.0).