Вариативные шаблоны
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 67 из 104.
Что делать, если вам нужна function, принимающая любое количество arguments любых типов? Традиционные шаблоны требуют точно указать, сколько параметров типов вам нужно. Вариативные шаблоны решают эту проблему, позволяя шаблонам принимать произвольное количество аргументов шаблона.
Синтаксис использует ... (многоточие) для создания parameter pack:
template <typename... Args>
void printAll(Args... args) {
// Args — это пакет параметров шаблона
// args — это пакет параметров функции
}Для обработки arguments обычно используется рекурсия с base case. parameter pack раскрывается по одному argument за раз, пока не останется ни одного:
// Базовый случай: аргументов не осталось
void print() {
std::cout << std::endl;
}
// Рекурсивный случай: обработать первый аргумент, рекурсивно вызвать с остальными
template <typename T, typename... Rest>
void print(T first, Rest... rest) {
std::cout << first << " ";
print(rest...); // Раскрыть оставшиеся аргументы
}
print(1, 3.14, "hello", 'x'); // Вывод: 1 3.14 hello xВы также можете использовать выражения fold (C++17) для более простых операций без явной рекурсии:
template <typename... Args>
auto sum(Args... args) {
return (args + ...); // Fold-выражение: складывает все аргументы
}
std::cout << sum(1, 2, 3, 4) << std::endl; // Вывод: 10Вариативные шаблоны лежат в основе многих возможностей стандартной библиотеки, таких как std::make_unique, std::tuple и std::function. Они позволяют создавать типобезопасные функции, работающие с любыми сочетаниями типов и количеством аргументов.
Задание
ЛегкоДавайте создадим гибкую систему ведения журналов, использующую вариативные шаблоны для обработки сообщений с любым количеством аргументов. Вы создадите утилиты, которые могут объединять значения, подсчитывать аргументы и выводить отформатированный результат — всё с использованием наборов параметров.
Вы организуете код в двух файлах:
Variadic.h: определите здесь свои вариативные шаблонные функции.Создайте функцию
printAll, которая выводит все свои аргументы, разделяя их пробелами, а затем переводит строку. Используйте рекурсивный подход с базовым случаем, который выводит только перевод строки, и рекурсивным случаем, который выводит первый аргумент, пробел, а затем рекурсивно обрабатывает оставшиеся аргументы.Создайте функцию
countArgs, которая возвращает количество переданных ей аргументов. Используйтеsizeof..., чтобы получить размер набора параметров.Создайте функцию
sum, которая складывает все числовые аргументы и возвращает результат. Используйте свёрточное выражение с оператором+.Создайте функцию
product, которая перемножает все числовые аргументы и возвращает результат. Используйте свёрточное выражение с оператором*.main.cpp: считайте четыре входных значения (каждое в отдельной строке):- Целое число
- Число с плавающей точкой двойной точности
- Строка
- Символ
Продемонстрируйте свои вариативные шаблоны:
- Вызовите
printAllсо всеми четырьмя входными значениями и выведите результат - Выведите количество аргументов при вызове
countArgsс целым числом, числом двойной точности и символом:Argument count: <count> - Вычислите сумму целого числа, числа двойной точности и значений 10 и 5, затем выведите:
Sum: <result> - Вычислите произведение целого числа и значений 2 и 3, затем выведите:
Product: <result> - Вызовите
printAllтолько со строкой (один аргумент) - Вызовите
printAllбез аргументов (должен быть выведен только перевод строки)
Например, для входных значений 5, 2.5, Hello и X:
5 2.5 Hello X
Argument count: 3
Sum: 22.5
Product: 30
Hello
Обратите внимание, как printAll обрабатывает любое количество аргументов разных типов — от четырёх аргументов до нуля. Функции sum и product используют свёрточные выражения, чтобы элегантно объединять все значения без явной рекурсии. Ваша функция countArgs демонстрирует, как sizeof... предоставляет размер набора параметров во время компиляции.
Попробуйте сами
#include <iostream>
#include <string>
#include "Variadic.h"
using namespace std;
int main() {
// Прочитать входные данные
int intVal;
double doubleVal;
string strVal;
char charVal;
cin >> intVal;
cin >> doubleVal;
cin >> strVal;
cin >> charVal;
// TODO: Вызвать printAll со всеми четырьмя входными данными
// TODO: Print argument count using countArgs with intVal, doubleVal, and charVal
// Format: "Argument count: <count>"
// TODO: Вычислить и вывести сумму intVal, doubleVal, 10 и 5
// Формат: "Sum: <result>"
// TODO: Вычислить и вывести произведение intVal, 2 и 3
// Формат: "Product: <result>"
// TODO: Вызвать printAll только со строкой
// TODO: Вызвать printAll без аргументов
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++