Вариативные шаблоны
Функция 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
}Пакет параметров
Такой шаблон называют вариативным. Запись typename... Ts объявляет пакет параметров — не один тип, а список типов любой длины, в том числе пустой. В списке параметров функции Ts... xs — пакет значений, по одному на каждый тип из Ts. Для вызова average(1, 2.5, 4) компилятор выводит Ts как int, double, int, а xs содержит три значения. Если тело не обращается к значениям пакета, имя можно не писать и в определении: Ts....
Для каждого набора типов компилятор инстанцирует отдельную функцию: average(1, 2) и average(1, 2.5, 4) вызывают две разные функции.
sizeof...(Ts) — число элементов пакета, а не размер в байтах, как у sizeof. Для average(1, 2.5, 4) оно равно 3, а у пустого пакета — 0. Это значение известно при компиляции.
Выражение свёртки
Перебрать пакет циклом нельзя: у его элементов могут быть разные типы. Вместо цикла пишут выражение свёртки: пакет, оператор и многоточие в обязательных круглых скобках. Для трёх значений (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. Поэтому начальное значение выбирают того типа, который нужен сумме: для склейки текста это пустая строка std::string{}, ведь два текста в кавычках через + не сложить.
Тип результата auto
Если тип результата шаблона заранее неизвестен, вместо него пишут auto. Компилятор выводит тип из return, как у лямбды: в template <typename... Ts> auto sum(Ts... xs) { return (xs + ... + 0); } для sum(1, 2) результат int, а для sum(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).
Главное
typename... Tsобъявляет пакет типов, аTs... xs— пакет значений любой длины.sizeof...(Ts)— число элементов пакета, известное при компиляции.- Выражение свёртки
(xs + ... + 0.0)складывает пакет без цикла, а начальное значение задаёт тип суммы. - Без начального значения свёртка пустого пакета через
+не компилируется.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.