Поделиться через


Многоточия и вариадические шаблоны

В этой статье показано, как использовать многоточие (...) в шаблонах C++ с переменным числом аргументов. Многоточие использовалось в C и C++. Они вводят переменные списки аргументов для функций. Одним из наиболее известных примеров является функция printf() из библиотеки времени выполнения C .

Шаблон variadic — это класс или шаблон функции, поддерживающий произвольное количество аргументов. Этот механизм особенно полезен разработчикам библиотекИ C++: его можно применить как к шаблонам классов, так и к шаблонам функций, и таким образом обеспечить широкий спектр типобезопасных и нетривиальных функциональных возможностей и гибкости.

Синтаксис

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

Ниже приведен базовый пример синтаксиса определения шаблона шаблона класса variadic:

template<typename... Arguments> class classname;

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

template<typename ...Arguments> class classname;

Или в следующем примере:

template<typename ... Arguments> class classname;

В этой статье используется соглашение, показанное в первом примере (многоточие присоединено к typename).

В предыдущих примерах Arguments — это пакет параметров. Класс classname может принимать переменное число аргументов, как показано в следующих примерах:

template<typename... Arguments> class vtclass;

vtclass< > vtinstance1;
vtclass<int> vtinstance2;
vtclass<float, bool> vtinstance3;
vtclass<long, std::vector<int>, std::string> vtinstance4;

С помощью определения шаблона шаблона класса variadic можно также требовать по крайней мере один параметр:

template <typename First, typename... Rest> class classname;

Ниже приведен базовый пример синтаксиса шаблона функции variadic:

template <typename... Arguments> returntype functionname(Arguments... args);

Затем Arguments пакет параметров развертывается для использования, как показано в следующем разделе.

Другие формы синтаксиса шаблона функции variadic возможны, в том числе, но не ограничиваются следующими примерами:

template <typename... Arguments> returntype functionname(Arguments&... args);
template <typename... Arguments> returntype functionname(Arguments&&... args);
template <typename... Arguments> returntype functionname(Arguments*... args);

Также const разрешены такие описатели:

template <typename... Arguments> returntype functionname(const Arguments&... args);

Шаблонные функции с переменным числом аргументов (как и аналогичные шаблонные классы) также могут устанавливать требование о том, что должен быть передан по меньшей мере один параметр.

template <typename First, typename... Rest> returntype functionname(const First& first, const Rest&... args);

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

template<typename... Arguments>
void tfunc(const Arguments&... args)
{
    constexpr auto numargs{ sizeof...(Arguments) };

    X xobj[numargs]; // array of some previously defined type X

    helper_func(xobj, args...);
}

Дополнительные сведения о положении многоточия

Ранее в этой статье описано многоточие размещения, определяющее пакеты параметров и расширения в этой форме: "слева от имени параметра, он обозначает пакет параметров и справа от имени параметра, он расширяет пакеты параметров в отдельные имена". Хотя технически верно, это может быть запутано в переводе в код. Необходимо учесть следующие моменты.

  • В списке параметров шаблона (template <parameter-list>) typename... представлен пакет параметров шаблона.

  • В предложении parameter-declaration-clause (func(parameter-list)), многоточие верхнего уровня представляет пакет параметров функции, а положение многоточия важно:

    // v1 is NOT a function parameter pack:
    template <typename... Types> void func1(std::vector<Types...> v1);
    
    // v2 IS a function parameter pack:
    template <typename... Types> void func2(std::vector<Types>... v2);
    
  • Там, где многоточие стоит непосредственно за именем параметра, оно используется для развертывания пакета параметров.

Пример

Хороший способ проиллюстрировать механизм шаблона функции variadic заключается в том, чтобы использовать его в перезаписи некоторых функций printf:

#include <iostream>

using namespace std;

void print() {
    cout << endl;
}

template <typename T> void print(const T& t) {
    cout << t << endl;
}

template <typename First, typename... Rest> void print(const First& first, const Rest&... rest) {
    cout << first << ", ";
    print(rest...); // recursive call using pack expansion syntax
}

int main()
{
    print(); // calls first overload, outputting only a newline
    print(1); // calls second overload

    // these call the third overload, the variadic template,
    // which uses recursion as needed.
    print(10, 20);
    print(100, 200, 300);
    print("first", 2, "third", 3.14159);
}

Выходные данные

1
10, 20
100, 200, 300
first, 2, third, 3.14159

Примечание.

Большинство реализаций, включающих шаблоны функций variadic, используют рекурсию какой-то формы, но это немного отличается от традиционной рекурсии. Традиционная рекурсия включает функцию, вызываемую самой сигнатурой. (Она может быть перегружена или шаблонна, но одна и та же сигнатура выбирается каждый раз.) Рекурсия Variadic включает вызов шаблона функции variadic с помощью разных (почти всегда уменьшающихся) чисел аргументов и тем самым вымечая другую сигнатуру каждый раз. "Базовый случай" по-прежнему является обязательным, но природа рекурсии отличается.