Многоточия и шаблоны с переменными аргументами
В этой статье показано, как использовать многоточие (...) в шаблонах C++ с переменным числом аргументов. Многоточие активно использовалось в C и C++. Они вводят переменные списки аргументов для функций. Одним из наиболее известных примеров является функция printf() из библиотеки времени выполнения C .
Шаблон с переменным числом аргументов — это шаблон класса или функции, поддерживающий произвольное число аргументов. Этот механизм особенно удобен для разработчиков библиотек C++, поскольку его можно применить к как к шаблонам классов, так и к шаблонам функций. Таким образом, он предоставляет широкий спектр широкий спектр типобезопасных и нетривиальных функций и гибких возможностей.
Синтаксис
В шаблонах шаблонами с переменным числом аргументов многоточие используется двумя способами. Слева от имени параметра оно означает пакет параметров, а справа от имени параметра оно служит для развертывания пакетов параметров в отдельные имена.
Ниже представлен простейший пример синтаксиса для определения шаблонного класса с переменным числом аргументов.
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;
Кроме того, определение шаблонного класса с переменным числом аргументов может устанавливать требование о том, что должен быть передан по меньшей мере один параметр:
template <typename First, typename... Rest> class classname;
Ниже представлен простейший пример синтаксиса для определения шаблонной функции с переменным числом аргументов.
template <typename... Arguments> returntype functionname(Arguments... args);
Далее пакет параметров Arguments затем развертывается для использования, как показано в следующем разделе, Основные сведения о шаблонах с переменным числом аргументов.
Возможны и другие формы синтаксиса шаблонной функции с переменным количеством аргументов возможны. Некоторые примеры приведены ниже.
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)
{
const unsigned numargs = sizeof...(Arguments);
X xobj[numargs]; // array of some previously defined type X
helper_func(xobj, args...);
}
Дополнительные сведения о положении многоточия
Выше в этой статье говорилось, что если многоточие определяет пакеты параметров и их развертывание, то "Слева от имени параметра оно означает пакет параметров, а справа от имени параметра оно служит для развертывания пакетов параметров в отдельные имена". Технически это верно, но может порождать неоднозначности при трансляции в код. Рассмотрим этот пример:
В списке-параметров-шаблона (template <parameter-list>) параметр typename... вводит в программу пакет параметров шаблона.
В предложении объявления параметра (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);
Там, где многоточие стоит непосредственно за именем параметра, оно используется для развертывания пакета параметров.
Пример
Механизм действия шаблонных функций с переменным числом аргументов можно проиллюстрировать на показательном примере — переписать с ее использованием какую-либо функциональность 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
Примечание
Большинство реализаций, которые включают шаблонные функции с переменным числом аргументов, используют рекурсии в том или ином виде, однако она отличается от стандартной рекурсии. Стандартная рекурсия включает функцию, которая вызывает сама себя с использованием той же сигнатуры. (Это может быть перегрузка или шаблон, но каждый раз выбирается одна и та же сигнатура.) Рекурсия с переменным числом аргументов заключается в вызове шаблона функции с переменным числом аргументов посредством использования другого (почти всегда уменьшающегося) числа аргументов. Таким образом, каждый раз отбрасывается новая сигнатура."Базовый случай" по-прежнему является обязательным, но природа рекурсии отличается.