Многоточия и вариадические шаблоны
В этой статье показано, как использовать многоточие (...
) в шаблонах 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 с помощью разных (почти всегда уменьшающихся) чисел аргументов и тем самым вымечая другую сигнатуру каждый раз. "Базовый случай" по-прежнему является обязательным, но природа рекурсии отличается.