Вариативные шаблоны
В C++11 — рекурсивный вызов функции с уменьшающимся количеством аргументов. На примере функции to_strings:
template<typename T, typename... Args>
void to_strings(T value, Args... args) {
// махинации с value
to_strings(args...); // вызов функции от всех аргументов, кроме T value
}
По факту мы каждый раз преобразовываем только value, а потом запускаем эту же функцию от всех аргументов, кроме value. Тогда первый аргумент из args становится value. И так далее. Нужна базовая функция от 0 аргументов (или специализация на одном аргументе) — это не очень удобно.
Parameter pack: возможности
- Сам
...— оператор «развёртки» пакета. - Модификаторы
const/volatileработают с пакетом обычно. sizeof...(Args)— количество элементов в пакете (а не их суммарный размер).fold-expression— компактная свёртка пакета через бинарный оператор.
Где может использоваться parameter pack:
- Параметр шаблона — 0..n шаблонных аргументов.
- Аргументы функции — 0..n аргументов.
- В
if constexpr. - В
sizeof...— количество элементов в пакете. - При вызове функции через
f(args...)— она получит все аргументы пакета.
Fold-expression (C++17)
Применяет бинарный оператор ко всем элементам пакета одним выражением, без рекурсии.
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // унарная fold справа
}
Виды fold:
| Запись | Раскрытие |
|---|---|
(args op ...) | args0 op (args1 op (args2 op args3)) — правая |
(... op args) | ((args0 op args1) op args2) op args3 — левая |
(args op ... op init) | args0 op (args1 op (args2 op init)) — правая с инитом |
(init op ... op args) | ((init op args0) op args1) op args2 — левая с инитом |
Применимо и для унарных, и для бинарных операторов.
Разная расстановка скобок важна для неассоциативных операций:
- Правая:
(args / ...)→8 / (4 / 2) = 4 - Левая:
(... / args)→(8 / 4) / 2 = 1
Comma fold pattern
(func(args), ...) — последовательность вызовов:
func(arg0), func(arg1), func(arg2), …
Удобно, когда хочется выполнить действие для каждого элемента пакета.
template<typename... Args>
void print_all(Args... args) {
((std::cout << args << ' '), ...);
}
Класс с переменным числом параметров — std::tuple
Главный пример вариативного шаблонного класса. Наивная реализация — через рекурсию-наследование:
// Базовый случай — 0 аргументов
template<typename... Ts>
struct tuple {};
// Специализация: голова + хвост
template<typename Head, typename... Tail>
struct tuple<Head, Tail...> : tuple<Tail...> {
Head value;
tuple(Head h, Tail... t)
: tuple<Tail...>(t...), value(h)
{}
};
- Класс наследуется от
tupleс меньшим числом аргументов. - В конструкторе инициализируем своё поле и базовый класс.
- Чтобы получить более глубокие поля, можно
static_castструктуры к её базовому классу. - Альтернатива — хранить поле
base(или ссылку на него) и получать значения какt.base.base.value. - Для геттера по индексу делаем шаблонный аргумент-индекс и
usingпо нему; нужна также специализация дляIndex == 0.
(Полная реализация std::tuple — на следующей лекции.)