Пакет (начиная с C++11)
Пакет — это сущность C++, определяющая одно из следующего:
- пакет параметров
- пакет параметров шаблона
- пакет параметров функции
| (начиная с C++20) |
| (начиная с C++26) |
Пакет параметров шаблона — это параметр шаблона, принимающий ноль или более аргументов шаблона (значений, типов или шаблонов). Пакет параметров функции — это параметр функции, принимающий ноль или более аргументов функции.
| Пакет инициализирующих захватов лямбда-выражения — это захват лямбда-выражения, вводящий инициализирующий захват для каждого элемента раскрытия пакета своего инициализатора. | (начиная с C++20) |
| Пакет структурированной привязки — это идентификатор в объявлении структурированной привязки, вводящий ноль или более структурированных привязок. | (начиная с C++26) |
Число элементов пакета равно:
- числу аргументов, переданных пакету параметров, если пакет является пакетом параметров шаблона или функции;
| (начиная с C++20) |
| (начиная с C++26) |
Шаблон, содержащий хотя бы один пакет параметров, называется вариативным шаблоном.
Синтаксис
Пакет параметров шаблона (используется в списках параметров шаблона псевдонима, шаблона класса, шаблона переменной(начиная с C++14), концепта(начиная с C++20) и шаблона функции)
type ... pack-name (необязательно) | (1) | |
type-parameter-key ... pack-name (необязательно) | (2) | |
type-constraint ... pack-name (необязательно) | (3) | (начиная с C++20) |
template < parameter-list > class ... pack-name (необязательно) | (4) | (до C++17) |
template < parameter-list > type-parameter-key ... pack-name (необязательно) | (4) | (начиная с C++17) |
Пакет параметров функции (форма декларатора, используется в списке параметров функции вариативного шаблона функции)
pack-name ... pack-param-name (необязательно) | (5) |
| Синтаксис пакетов, не являющихся пакетами параметров, см. в описании пакета инициализирующих захватов лямбда-выражения и пакета структурированной привязки(начиная с C++26). | (начиная с C++20) |
Раскрытие пакета (используется в теле шаблона)
pattern ... | (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 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)
}
Если число элементов в пакете равно нулю (пустой пакет), инстанцирование раскрытия пакета не изменяет синтаксическую интерпретацию окружающей конструкции, даже если полное отсутствие раскрытия пакета в противном случае привело бы к некорректному коду или неоднозначности синтаксиса. Инстанцирование создаёт пустой список.
template<class... Bases>
struct X : Bases... { };
template<class... Args>
void f(Args... args)
{
X<Args...> x(args...);
}
template void f<>(); // OK, X<> has no base classes
// x is a variable of type X<> that is value-initialized
Контексты раскрытия
В зависимости от места раскрытия результирующий список, разделённый запятыми (или пробелами для спецификаторов выравнивания), представляет собой список определённого вида: список параметров функции, список инициализаторов членов, список атрибутов и т. д. Ниже перечислены все допустимые контексты:
Списки аргументов функции
Раскрытие пакета может находиться внутри скобок оператора вызова функции. В этом случае шаблоном для раскрытия является самое большое выражение или список инициализации в фигурных скобках слева от многоточия:
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) |
Индексация пакетаПри индексации пакета раскрытие пакета содержит нераскрытый пакет, за которым следуют многоточие и индекс. Шаблоном выражения индексации пакета является идентификатор, а шаблоном спецификатора индексации пакета — имя typedef. consteval auto first_plus_last(auto... args)
{
return args...[0] + args...[sizeof...(args) - 1];
}
static_assert(first_plus_last(5) == 10);
static_assert(first_plus_last(5, 4) == 9);
static_assert(first_plus_last(5, 6, 2) == 7);
Объявления friendВ объявлениях friend класса за каждым спецификатором типа может следовать многоточие: struct C {};
struct E { struct Nested; };
template<class... Ts>
class R
{
friend Ts...;
};
template<class... Ts, class... Us>
class R<R<Ts...>, R<Us...>>
{
friend Ts::Nested..., Us...;
};
R<C, E> rce; // classes C and E are friends of R<C, E>
R<R<E>, R<C, int>> rr; // E::Nested and C are friends of R<R<E>, R<C, int>>
Свёрнутые раскрытые ограниченияВ свёрнутых раскрытых ограничениях шаблоном является ограничение этого свёрнутого раскрытого ограничения. Свёрнутое раскрытое ограничение не инстанцируется. Список аннотацийРаскрытия пакетов допускаются в списках аннотаций: template<auto... Ann> [[=Ann...]] int x; | (начиная с C++26) |
Примечания
| Макрос проверки поддержки возможностей | Значение | Стандарт | Возможность |
|---|---|---|---|
__cpp_variadic_templates | 200704L | (C++11) | Вариативные шаблоны |
__cpp_pack_indexing | 202311L | (C++26) | Индексация пакета |
Пример
В приведённом ниже примере определяется функция, похожая на 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++.
| DR | Применяется к | Опубликованное поведение | Корректное поведение |
|---|---|---|---|
| CWG 1533 | C++11 | раскрытие пакета могло находиться в инициализаторе члена | не допускается |
| CWG 2717 | C++11 | инстанцирования спецификаторов выравнивания разделялись запятыми | они разделяются пробелами |
См. также
| Шаблон функции | Определяет семейство функций |
| Шаблон класса | Определяет семейство классов |
sizeof... | Получает число элементов в пакете |
| Функция с переменным числом аргументов в стиле C | Принимает переменное число аргументов |
| Макросы препроцессора | Также могут быть вариативными |
| Свёрточное выражение | Сворачивает пакет с помощью бинарного оператора |
| Индексация пакета | Обращается к элементу пакета по заданному индексу |
© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/cpp/language/pack