Варіативні шаблони (Variadic Templates) є потужною особливістю C++, введеною в C++11, яка дозволяє писати функції та класи, що можуть приймати будь-яку кількість параметрів шаблону будь-якого типу. Ця можливість дозволяє створювати більш гнучкі та загальні патерни програмування, зберігаючи типову безпеку на етапі компіляції.
Основні поняття
Пакет параметрів (Parameter Pack)
Пакет параметрів — це шаблонний параметр, який може приймати нуль або більше аргументів. Він позначається трьома крапками (...
). Існують два типи пакетів параметрів:
- Пакет параметрів шаблону: використовується в оголошенні шаблону
- Пакет параметрів функції: використовується в списку параметрів функції
Розширення пакета (Pack Expansion)
Розширення пакета — це процес розширення пакета параметрів у окремі елементи. Воно ініціюється використанням трьох крапок (...
) після шаблону, який використовує пакет.
Базові приклади
Розглянемо простий приклад варіативної функції-шаблону, яка виводить всі свої аргументи:
#include
// Базовий випадок: коли немає аргументів
void print() {
std::cout << std::endl;
}
// Рекурсивний варіативний шаблон
template <typename T, typename... Args>
void print(T first, Args... rest) {
std::cout << first << " ";
print(rest...); // Рекурсивний виклик з залишковими аргументами
}
int main() {
print(1, 2.5, "hello", 'c'); // Виведе: 1 2.5 hello c
return 0;
}
Розширене використання: Реалізація кортежу (Tuple Implementation)
Ось спрощена реалізація структури, подібної до кортежу, з використанням варіативних шаблонів:
template <typename... Types>
class VariadicTuple;
// Базовий випадок: порожній кортеж
template<>
class VariadicTuple<> {
public:
static constexpr size_t size = 0;
};
// Рекурсивний випадок
template <typename Head, typename... Tail>
class VariadicTuple {
Head head;
VariadicTuple<Tail...> tail;
public:
static constexpr size_t size = sizeof...(Tail) + 1;
VariadicTuple(const Head& h, const Tail&... t)
: head(h), tail(t...) {}
Head& getHead() { return head; }
VariadicTuple<Tail...>& getTail() { return tail; }
};
Досконале переспрямування з варіативними шаблонами (Perfect Forwarding with Variadic Templates)
Варіативні шаблони часто використовуються разом з досконалим переспрямуванням (perfect forwarding), щоб створювати обгорткові функції:
template <typename... Args>
void wrapper(Args&&... args) {
// Переспрямовуємо всі аргументи, зберігаючи їхні категорії значень
someFunction(std::forward<Args>(args)...);
}
Вирази згортки (C++17)
template <typename... Args>
auto sum(Args... args) {
return (... + args); // Одностороннє ліве згортання
}
template <typename... Args>
bool all(Args... args) {
return (... && args); // Одностороннє ліве згортання
}
// Використання
int total = sum(1, 2, 3, 4, 5); // 15
bool allTrue = all(true, true, false); // false
Звичайні випадки використання
- Фабричні функції (Factory Functions)
template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
2. Гетерогенний контейнер (Heterogeneous Container)
template <typename... Types>
class Variant {
std::tuple<Types...> data;
public:
template <std::size_t I>
auto& get() {
return std::get<I>(data);
}
};
Кращі практики
- Завжди надавайте базовий випадок для рекурсивних варіативних шаблонів
- Використовуйте
sizeof...(args)
для отримання кількості аргументів у пакеті - Розглядайте використання виразів згортки (C++17), коли це доречно
- Будьте уважні до часу компіляції та глибини інстанціації шаблонів
- Використовуйте
std::forward
, коли потрібне досконале переспрямування
Перекладено з: Understanding Variadic Templates in C++