Перейти к содержимому
Теория · Шаблоны · урок 9 из 11

Вариативные шаблоны

Функция double average(double a, double b) считает среднее двух чисел. Для трёх чисел понадобится ещё одна перегрузка, для пяти — ещё одна. Шаблон с многоточием после typename принимает любое число аргументов любых типов:

C++
#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.

Частая ошибка

Свёртку записали без начального значения:

C++
#include <iostream>

template <typename... Ts>
double total(Ts... xs) {
    return (xs + ...);  // ✗
}

int main() {
    std::cout << total(1, 2) << total() << '\n';
}
TEXT
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 и подпишись на канал — откроются все уроки.

Войти другим способом