Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 67 из 104.

Что если вам нужна функция, которая принимает любое количество аргументов любых типов? Традиционные шаблоны требуют точного указания количества параметров типа. Вариативные шаблоны решают эту проблему, позволяя шаблонам принимать произвольное количество аргументов шаблона.

Синтаксис использует ... (многоточие) для создания пакета параметров:

template <typename... Args>
void printAll(Args... args) {
    // Args — это пакет параметров шаблона
    // args — это пакет параметров функции
}

Для обработки аргументов обычно используется рекурсия с базовым случаем. Пакет параметров разворачивается по одному аргументу за раз, пока они не закончатся:

// Базовый случай: аргументов не осталось
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

Вы также можете использовать выражения свёртки (C++17) для более простых операций без явной рекурсии:

template <typename... Args>
auto sum(Args... args) {
    return (args + ...);  // Выражение свёртки: складывает все аргументы
}

std::cout << sum(1, 2, 3, 4) << std::endl;  // Вывод: 10

Вариативные шаблоны лежат в основе многих возможностей стандартной библиотеки, таких как std::make_unique, std::tuple и std::function. Они позволяют создавать типобезопасные функции, которые работают с любым сочетанием типов и количества аргументов.

challenge icon

Задание

Легко

Давайте создадим гибкую систему логирования, которая использует вариативные шаблоны для обработки сообщений с любым количеством аргументов. Вы создадите утилиты, которые могут объединять значения, подсчитывать количество аргументов и выводить отформатированный результат — и все это с использованием пакетов параметров (parameter packs).

Вы организуете свой код в двух файлах:

  • Variadic.h: Определите здесь свои функции вариативных шаблонов.

    Создайте функцию printAll, которая выводит все свои аргументы, разделенные пробелами, за которыми следует символ новой строки. Используйте рекурсивный подход с базовым случаем, который выводит только новую строку, и рекурсивным случаем, который выводит первый аргумент, пробел, а затем рекурсивно вызывает себя с остальными аргументами.

    Создайте функцию countArgs, которая возвращает количество переданных ей аргументов. Используйте sizeof... для получения размера пакета параметров.

    Создайте функцию sum, которая складывает все числовые аргументы и возвращает результат. Используйте сверточное выражение (fold expression) с оператором +.

    Создайте функцию product, которая перемножает все числовые аргументы и возвращает результат. Используйте сверточное выражение (fold expression) с оператором *.

  • main.cpp: Считайте четыре входных значения (каждое на отдельной строке):
    1. Целое число (integer)
    2. Число с плавающей точкой (double)
    3. Строку (string)
    4. Символ (character)

    Продемонстрируйте работу ваших вариативных шаблонов следующим образом:

    1. Вызовите printAll со всеми четырьмя входными данными и выведите результат.
    2. Выведите количество аргументов при вызове countArgs с целым числом, числом с плавающей точкой и символом в формате: Argument count: <count>
    3. Вычислите сумму целого числа, числа с плавающей точкой и значений 10 и 5, затем выведите: Sum: <result>
    4. Вычислите произведение целого числа и значений 2 и 3, затем выведите: Product: <result>
    5. Вызовите printAll только со строкой (один аргумент).
    6. Вызовите 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... позволяет получить размер пакета во время компиляции.

Шпаргалка

Вариативные шаблоны позволяют шаблонам принимать произвольное количество аргументов шаблона, используя синтаксис ... (многоточие) для создания пакета параметров:

template <typename... Args>
void printAll(Args... args) {
    // Args — это пакет параметров шаблона
    // args — это пакет параметров функции
}

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

// Базовый случай: аргументов не осталось
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

Используйте sizeof... для получения количества аргументов в пакете параметров:

template <typename... Args>
size_t countArgs(Args... args) {
    return sizeof...(args);
}

Используйте выражения свертки (fold expressions) (C++17) для более простых операций без явной рекурсии:

template <typename... Args>
auto sum(Args... args) {
    return (args + ...);  // Выражение свертки: складывает все аргументы
}

std::cout << sum(1, 2, 3, 4) << std::endl;  // Вывод: 10

Выражения свертки работают и с другими операторами, например, с умножением:

template <typename... Args>
auto product(Args... args) {
    return (args * ...);  // Перемножает все аргументы
}

Попробуйте сами

#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: Вывести количество аргументов, используя countArgs с intVal, doubleVal и charVal
    // Формат: "Argument count: <count>"
    
    // TODO: Вычислить и вывести сумму intVal, doubleVal, 10 и 5
    // Формат: "Sum: <result>"
    
    // TODO: Вычислить и вывести произведение intVal, 2 и 3
    // Формат: "Product: <result>"
    
    // TODO: Вызвать printAll только со строкой
    
    // TODO: Вызвать printAll без аргументов
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование