Упакованный параметр (с C++11)
Упакованный параметр шаблона — это параметр шаблона, который принимает ноль или более аргументов шаблона (нетипов, типов или шаблонов). Упакованный параметр функции — это параметр функции, который принимает ноль или более аргументов функции.
Шаблон с по крайней мере одним упакованным параметром называется шаблоном с переменным числом аргументов.
Синтаксис
Упакованный параметр шаблона (встречается в шаблоне псевдонима типа, шаблоне класса, шаблоне переменной(с C++14) и шаблоне функции списка параметров)
тип ... имя_пакета (необязательно) | (1) | |
typename|class ... имя_пакета (необязательно) | (2) | |
ограничение_типа ... имя_пакета (необязательно) | (3) | (с C++20) |
template < список_параметров > class ... имя_пакета (необязательно) | (4) | (до C++17) |
template < список_параметров > typename|class ... имя_пакета (необязательно) | (4) | (с C++17) |
Упакованный параметр функции (форма декларатора, встречается в списке параметров функции шаблонной функции с переменным числом аргументов)
имя_пакета ... имя_параметра_пакета (необязательно) | (5) |
Расширение упакованного параметра (встречается в теле шаблона с переменным числом аргументов)
шаблон ... | (6) |
| 3) Упакованный параметр шаблона типа с ограничениями с необязательным именем | (с C++20) |
patternов. Шаблон должен содержать по крайней мере один упакованный параметр.Объяснение
Шаблон класса с переменным числом аргументов может быть инстанцирован любым количеством аргументов шаблона:
template<class... Types>
struct Tuple {};
Tuple<> t0; // Types contains no arguments
Tuple<int> t1; // Types contains one argument: int
Tuple<int, float> t2; // Types contains two arguments: int and float
Tuple<0> t3; // error: 0 is not a typeШаблон функции с переменным числом аргументов может быть вызван с любым количеством аргументов функции (аргументы шаблона выводятся с помощью вывода аргументов шаблона):
template<class... Types> void f(Types... args); f(); // OK: args contains no arguments f(1); // OK: args contains one argument: int f(2, 1.0); // OK: args contains two arguments: int and double
В главном шаблоне класса упакованный параметр шаблона должен быть последним параметром в списке параметров шаблона. В шаблоне функции упакованный параметр шаблона может появиться раньше в списке при условии, что все последующие параметры могут быть выведены из аргументов функции или имеют значения по умолчанию:
template<typename U, typename... Ts> // OK: can deduce U
struct valid;
// template<typename... Ts, typename U> // Error: Ts... not at the end
// struct Invalid;
template<typename... Ts, typename U, typename=void>
void valid(U, Ts...); // OK: can deduce U
// void valid(Ts..., U); // Can't be used: Ts... is a non-deduced context in this position
valid(1.0, 1, 2, 3); // OK: deduces U as double, Ts as {int, int, int}Если каждая допустимая специализация шаблона с переменным числом аргументов требует пустого упакованного параметра шаблона, программа имеет неверную форму; диагностическое сообщение не требуется.
Расширение пакета
Шаблон, за которым следует многоточие, в котором имя хотя бы одного упакованного параметра появляется по крайней мере один раз, расширяется до нуля или более экземпляров шаблона, где имя упакованного параметра заменяется каждым элементом из пакета в порядке следования. Экземпляры спецификаторов выравнивания разделяются пробелами, другие экземпляры — запятыми.
template<class... Us>
void f(Us... pargs) {}
template<class... Ts>
void g(Ts... args)
{
f(&args...); // “&args...” is a pack expansion
// “&args” is its pattern
}
g(1, 0.2, "a"); // Ts... args expand to int E1, double E2, const char* E3
// &args... expands to &E1, &E2, &E3
// Us... pargs expand to int* E1, double* E2, const char** E3Если в одном шаблоне появляются имена двух упакованных параметров, они расширяются одновременно, и они должны иметь одинаковую длину:
template<typename...>
struct Tuple {};
template<typename T1, typename T2>
struct Pair {};
template<class... Args1>
struct zip
{
template<class... Args2>
struct with
{
typedef Tuple<Pair<Args1, Args2>...> type;
// Pair<Args1, Args2>... is the pack expansion
// Pair<Args1, Args2> is the pattern
};
};
typedef zip<short, int>::with<unsigned short, unsigned>::type T1;
// Pair<Args1, Args2>... expands to
// Pair<short, unsigned short>, Pair<int, unsigned int>
// T1 is Tuple<Pair<short, unsigned short>, Pair<int, unsigned>>
// typedef zip<short>::with<unsigned short, unsigned>::type T2;
// error: pack expansion contains parameter packs of different lengthsЕсли расширение пакета вложено в другое расширение пакета, упакованные параметры, которые появляются внутри самого внутреннего расширения пакета, расширяются им, и должен быть другой упакованный параметр, упомянутый во внешнем расширении пакета, но не во внутреннем:
template<class... Args>
void g(Args... args)
{
f(const_cast<const Args*>(&args)...);
// const_cast<const Args*>(&args) is the pattern, it expands two packs
// (Args and args) simultaneously
f(h(args...) + args...); // Nested pack expansion:
// inner pack expansion is "args...", it is expanded first
// outer pack expansion is h(E1, E2, E3) + args..., it is expanded
// second (as h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
}Места расширения
В зависимости от того, где происходит расширение, полученный список, разделенный запятыми (или пробелами для спецификаторов выравнивания), является списком другого типа: список аргументов функции, список инициализаторов членов, список атрибутов и т.д. Вот список всех разрешенных контекстов:
Списка аргументов функции
Расширение пакета может появиться внутри скобок оператора вызова функции, в этом случае наибольшее выражение или список с фигурными скобками слева от многоточия является шаблоном, который расширяется:
f(args...); // expands to f(E1, E2, E3) f(&args...); // expands to f(&E1, &E2, &E3) f(n, ++args...); // expands to f(n, ++E1, ++E2, ++E3); f(++args..., n); // expands to f(++E1, ++E2, ++E3, n); f(const_cast<const Args*>(&args)...); // f(const_cast<const E1*>(&X1), const_cast<const E2*>(&X2), const_cast<const E3*>(&X3)) f(h(args...) + args...); // expands to // f(h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
Формально, список_выражений в выражении вызова функции классифицируется как список_инициализаторов, а шаблон — это инициализирующая_часть, которая является либо выражением_присваивания, либо списком_с_фигурными_скобками.
Скобочные инициализаторы
Расширение пакета может появиться внутри скобок прямой инициализации, приведения типов со стилем функции и других контекстов (инициализатор члена, оператор new и т.д.), в этом случае правила идентичны правилам для выражения вызова функции выше:
Class c1(&args...); // calls Class::Class(&E1, &E2, &E3) Class c2 = Class(n, ++args...); // calls Class::Class(n, ++E1, ++E2, ++E3); ::new((void *)p) U(std::forward<Args>(args)...) // std::allocator::allocate
Список с фигурными скобками
В списке_с_фигурными_скобками (списке инициализаторов в фигурных скобках и других списках_с_фигурными_скобками, используемых в инициализации_списком и некоторых других контекстах), также может появиться расширение пакета:
template<typename... Ts>
void func(Ts... args)
{
const int size = sizeof...(args) + 2;
int res[size] = {1, args..., 2};
// since initializer lists guarantee sequencing, this can be used to
// call a function on each element of a pack, in order:
int dummy[sizeof...(Ts)] = {(std::cout << args, 0)...};
}Списка аргументов шаблона
Расширения пакетов могут использоваться в любом месте списка аргументов шаблона, при условии, что шаблон имеет параметры, соответствующие расширению:
template<class A, class B, class... C>
void func(A arg1, B arg2, C... arg3)
{
container<A, B, C...> t1; // expands to container<A, B, E1, E2, E3>
container<C..., A, B> t2; // expands to container<E1, E2, E3, A, B>
container<A, C..., B> t3; // expands to container<A, E1, E2, E3, B>
}Список параметров функции
В списке параметров функции, если в объявлении параметра появляется многоточие (будь то имя упакованного параметра функции (как в, Args... args) или нет), то объявление параметра является шаблоном:
template<typename... Ts>
void f(Ts...) {}
f('a', 1); // Ts... expands to void f(char, int)
f(0.1); // Ts... expands to void f(double)
template<typename... Ts, int... N>
void g(Ts (&...arr)[N]) {}
int n[1];
g<const char, int>("a", n); // Ts (&...arr)[N] expands to
// const char (&)[2], int(&)[1]Примечание: В шаблоне Ts (&...arr)[N], многоточие является самым внутренним элементом, а не последним, как во всех других расширениях пакета.
Примечание: Ts (&...)[N] не разрешено, потому что грамматика C++11 требует, чтобы скобочное многоточие имело имя: вопрос CWG 1488.
Список параметров шаблона
Расширение пакета может появиться в списке параметров шаблона:
template<typename... T>
struct value_holder
{
template<T... Values> // expands to a non-type template parameter
struct apply {}; // list, such as <int, char, int(&)[5]>
};Спецификаторы базовых классов и списки инициализаторов членов
Расширение пакета может указать список базовых классов в объявлении класса. Как правило, это также означает, что конструктор должен использовать расширение пакета в списке инициализаторов членов для вызова конструкторов этих базовых классов:
template<class... Mixins>
class X : public Mixins...
{
public:
X(const Mixins&... mixins) : Mixins(mixins)... {}
};Захват лямбда-выражений
Расширение пакета может появиться в списке захвата выражения лямбда:
template<class... Args>
void f(Args... args)
{
auto lm = [&, args...] { return g(args...); };
lm();
}Оператор sizeof...
Оператор sizeof... также классифицируется как расширение пакета:
template<class... Types>
struct count
{
static const std::size_t value = sizeof...(Types);
}; Динамические спецификации исключенийСписок исключений в динамической спецификации исключений также может быть расширением пакета: template<class... X>
void func(int arg) throw(X...)
{
// ... throw different Xs in different situations
} | (до C++17) |
Спецификатор выравнивания
Расширения пакета разрешены в списках типов и списках выражений, используемых ключевым словом alignas. Экземпляры разделяются пробелами:
template<class... T>
struct Align
{
alignas(T...) unsigned char buffer[128];
};
Align<int, short> a; // the alignment specifiers after expansion are
// alignas(int) alignas(short)
// (no comma in between)Список атрибутов
Расширения пакета разрешены в списках атрибутов, если это разрешено спецификацией атрибута. Например:
template<int... args> [[vendor::attr(args)...]] void* f();
Выражения слиянияВ выражениях слияния, шаблон — это всё подвыражение, не содержащее нераспакованный параметр-упаковку. Объявления usingВ объявлении using, многоточие может появиться в списке деклараторов, это полезно при наследовании от параметра-упаковки: template<typename... bases>
struct X : bases...
{
using bases::g...;
};
X<B, D> x; // OK: B::g and D::g introduced | (с C++17) |
Индексирование упаковокВ индексировании упаковок, развёртывание упаковки содержит нераспакованный параметр-упаковку, за которым следует многоточие и индекс. Шаблон выражения индексирования упаковок — это идентификатор, а шаблон спецификатора индексирования упаковок — это имя типа. consteval auto first_plus_last(auto... args) {
return args...[0] + args...[sizeof...(args) - 1];
}
static_assert(first_plus_last(5) == 5);
static_assert(first_plus_last(5, 4) == 9);
static_assert(first_plus_last(5, 6, 2) == 7); | (с C++26) |
Примечания
| Макрокоманда проверки функции | Значение | Стандарт | Функция |
|---|---|---|---|
__cpp_variadic_templates | 200704L | (C++11) | Многопараметрические шаблоны |
Пример
В примере ниже определена функция, аналогичная std::printf, которая заменяет каждое вхождение символа % в строке формата значением.
Первый перегруз вызывается, когда передаётся только строка формата и не происходит развёртывание параметров.
Второй перегруз содержит отдельный шаблонный параметр для головного элемента аргументов и параметр-упаковку, это позволяет рекурсивным вызовам передавать только хвостовую часть параметров, пока она не станет пустой.
Targs — это шаблонный параметр-упаковку, а Fargs — параметр-упаковку функции.
#include <iostream>
void tprintf(const char* format) // base function
{
std::cout << format;
}
template<typename T, typename... Targs>
void tprintf(const char* format, T value, Targs... Fargs) // recursive variadic function
{
for (; *format != '\0'; format++)
{
if (*format == '%')
{
std::cout << value;
tprintf(format + 1, Fargs...); // recursive call
return;
}
std::cout << *format;
}
}
int main()
{
tprintf("% world% %\n", "Hello", '!', 123);
}Вывод:
Hello world! 123
Отчёты об ошибках
Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.
| Отчёт об ошибке | Применено к | Поведение, как опубликовано | Корректное поведение |
|---|---|---|---|
| CWG 1533 | C++11 | расширение упаковки могло произойти в инициализаторе члена для члена | не разрешено |
| CWG 2717 | C++11 | инстанциации спецификаторов выравнивания разделялись запятыми | они разделяются пробелами |
См. также
| шаблон функции | |
| шаблон класса | |
sizeof... | Определяет количество элементов в параметре-упаковке. |
| Функции со переменным числом аргументов (C-style) | |
| Макросы препроцессора | Также могут быть многопараметрическими |
| Выражения слияния | |
| Индексирование упаковок | Доступ к элементу параметра-упаковки по указанному индексу |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/parameter_pack