Pack (since C++11)
Пакет — это сущность C++, которая определяет одно из следующих:
- пакет параметров
-
- шаблонный пакет параметров
- функциональный пакет параметров
| (начиная с C++20) |
| (since C++26) |
Параметр шаблона с многоточием — это параметр шаблона, который принимает ноль или более аргументов шаблона (константы, типы или шаблоны). Параметр функции с многоточием — это параметр функции, который принимает ноль или более аргументов функции.
|
Лямбда-захват с инициализацией для пачки - это лямбда-захват, который вводит захват с инициализацией для каждого элемента в расширении пачки его инициализатора. |
(since C++20) |
|
Связывание структуры пакета - это идентификатор в объявлении структурированного связывания, который вводит ноль или более структурированных связываний. |
(since C++26) |
Количество элементов пака равно:
- количество аргументов, предоставленных для пачки параметров, если пачка является шаблонной или функциональной пачкой параметров,
|
(since C++20) |
|
(since C++26) |
Шаблон с как минимум одним пакетом параметров называется вариативным шаблоном .
Синтаксис
Параметр шаблона-пакет (встречается в псевдонимах шаблонов , шаблонах классов , шаблонах переменных (начиная с C++14) , концепциях (начиная с C++20) и шаблонах функций в списках параметров)
тип
...
имя-пакета
(опционально)
|
(1) | ||||||||
typename
|
class
...
имя-пакета
(опционально)
|
(2) | ||||||||
ограничение-типа
...
имя-пакета
(опционально)
|
(3) | (начиная с C++20) | |||||||
template
<
список-параметров
>
class
...
имя-пакета
(опционально)
|
(4) | (до C++17) | |||||||
template
<
список-параметров
>
typename
|
class
...
имя-пакета
(опционально)
|
(4) | (начиная с C++17) | |||||||
Параметр-пакет функции (форма декларатора , появляется в списке параметров функции вариативного шаблона функции)
pack-name
...
pack-param-name
(опционально)
|
(5) | ||||||||
|
Для синтаксиса непараметрических паков см. lambda init-capture pack и structured binding pack (начиная с C++26) . |
(начиная с C++20) |
Развертывание пакета (появляется в теле шаблона)
pattern
...
|
(6) | ||||||||
|
3)
Пакет параметров шаблона типа с ограничениями с необязательным именем
|
(since C++20) |
pattern
s. Шаблон должен включать как минимум один пакет.
Объяснение
Вариативный шаблон класса может быть инстанцирован с любым количеством аргументов шаблона:
template<class... Types> struct Tuple {}; Tuple<> t0; // Types не содержит аргументов Tuple<int> t1; // Types содержит один аргумент: int Tuple<int, float> t2; // Types содержит два аргумента: int и float Tuple<0> t3; // ошибка: 0 не является типом
Вариативный шаблон функции может быть вызван с любым количеством аргументов функции (аргументы шаблона выводятся через вывод аргументов шаблона ):
template<class... Types> void f(Types... args); f(); // OK: args не содержит аргументов f(1); // OK: args содержит один аргумент: int f(2, 1.0); // OK: args содержит два аргумента: int и double
В первичном шаблоне класса пакет параметров шаблона должен быть последним параметром в списке параметров шаблона. В шаблоне функции пакет параметров шаблона может появляться раньше в списке при условии, что все последующие параметры могут быть выведены из аргументов функции или имеют аргументы по умолчанию:
template<typename U, typename... Ts> // OK: можно вывести U struct valid; // template<typename... Ts, typename U> // Ошибка: Ts... не в конце // struct Invalid; template<typename... Ts, typename U, typename=void> void valid(U, Ts...); // OK: можно вывести U // void valid(Ts..., U); // Не может быть использовано: Ts... является невыводимым контекстом в этой позиции valid(1.0, 1, 2, 3); // OK: выводит U как double, Ts как {int, int, int}
Если каждая допустимая специализация вариативного шаблона требует пустой набор параметров шаблона, программа является некорректной, диагностика не требуется.
Развертывание пакета параметров
Шаблон, за которым следует многоточие, в котором имя по крайней мере одного пакета появляется по крайней мере один раз, раскрывается в ноль или более экземпляров шаблона, где имя пакета заменяется каждым из элементов пакета по порядку. Экземпляры спецификаторов выравнивания разделяются пробелами, другие экземпляры разделяются запятыми.
template<class... Us> void f(Us... pargs) {} template<class... Ts> void g(Ts... args) { f(&args...); // “&args...” — это раскрытие пачки // “&args” — это её образец } g(1, 0.2, "a"); // Ts... args раскрывается в int E1, double E2, const char* E3 // &args... раскрывается в &E1, &E2, &E3 // Us... pargs раскрывается в 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>... является расширением пакета // Pair<Args1, Args2> является шаблоном }; }; typedef zip<short, int>::with<unsigned short, unsigned>::type T1; // Pair<Args1, Args2>... раскрывается в // Pair<short, unsigned short>, Pair<int, unsigned int> // T1 является Tuple<Pair<short, unsigned short>, Pair<int, unsigned>> // typedef zip<short>::with<unsigned short, unsigned>::type T2; // ошибка: расширение пакета содержит пакеты разной длины
Если расширение пака вложено в другое расширение пака, паки, которые появляются внутри самого внутреннего расширения пака, расширяются им, и должен быть упомянут другой пак во внешнем расширении пака, но не в самом внутреннем:
template<class... Args> void g(Args... args) { f(const_cast<const Args*>(&args)...); // const_cast<const Args*>(&args) - это шаблон, он раскрывает два пакета // (Args и args) одновременно f(h(args...) + args...); // Вложенное раскрытие пакета: // внутреннее раскрытие пакета "args..." выполняется первым // внешнее раскрытие пакета h(E1, E2, E3) + args... выполняется // вторым (как 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<> не имеет базовых классов // x - переменная типа X<>, которая инициализируется значением по умолчанию
Локусы расширения
В зависимости от того, где происходит раскрытие, результирующий список, разделённый запятыми (или пробелами для спецификаторов выравнивания ), представляет собой различные типы списков: список параметров функции, список инициализации членов, список атрибутов и т.д. Ниже приведён перечень всех допустимых контекстов:
Списки аргументов функции
Расширение пакета может появляться внутри скобок оператора вызова функции, и в этом случае наибольшее выражение или список инициализации в фигурных скобках слева от многоточия является шаблоном, который расширяется:
f(args...); // раскрывается в f(E1, E2, E3) f(&args...); // раскрывается в f(&E1, &E2, &E3) f(n, ++args...); // раскрывается в f(n, ++E1, ++E2, ++E3); f(++args..., n); // раскрывается в 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...); // раскрывается в // f(h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
Инициализаторы в круглых скобках
Расширение пакета может появляться внутри скобок прямой инициализации , функционального приведения и в других контекстах ( инициализатор члена , new-выражение и т.д.), в этом случае правила идентичны правилам для выражения вызова функции выше:
Class c1(&args...); // вызывает Class::Class(&E1, &E2, &E3) Class c2 = Class(n, ++args...); // вызывает 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}; // поскольку списки инициализации гарантируют последовательность, это может быть использовано для // вызова функции для каждого элемента пака по порядку: int dummy[sizeof...(Ts)] = {(std::cout << args, 0)...}; }
Списки аргументов шаблонов
Pack-расширения могут использоваться в любом месте списка шаблонных аргументов, при условии что шаблон имеет соответствующие параметры для сопоставления с расширением:
template<class A, class B, class... C> void func(A arg1, B arg2, C... arg3) { container<A, B, C...> t1; // раскрывается в container<A, B, E1, E2, E3> container<C..., A, B> t2; // раскрывается в container<E1, E2, E3, A, B> container<A, C..., B> t3; // раскрывается в container<A, E1, E2, E3, B> }
Список параметров функции
В списке параметров функции, если многоточие появляется в объявлении параметра (независимо от того, именует ли оно пакет параметров функции (как в,
Args
...
args
) или нет), объявление параметра является шаблоном:
template<typename... Ts> void f(Ts...) {} f('a', 1); // Ts... раскрывается в void f(char, int) f(0.1); // Ts... раскрывается в 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] раскрывается в // const char (&)[2], int(&)[1]
Примечание: в шаблоне
Ts (&...arr)[N]
многоточие является самым внутренним элементом, а не последним элементом, как во всех других расширениях пакетов.
Примечание:
Ts (&...)[N]
не допускается, поскольку грамматика C++11 требует, чтобы у заключенного в скобки многоточия было имя:
Проблема CWG 1488
.
Список параметров шаблона
Расширение пакета может появляться в списке параметров шаблона:
template<typename... T> struct value_holder { template<T... Values> // раскрывается в список параметров шаблона-константы, struct apply {}; // такой как <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) |
Спецификатор выравнивания
Pack-расширения разрешены как в списках типов, так и в списках выражений, используемых ключевым словом
alignas
. Инстанциации разделяются пробелами:
template<class... T> struct Align { alignas(T...) unsigned char buffer[128]; }; Align<int, short> a; // спецификаторы выравнивания после раскрытия будут // alignas(int) alignas(short) // (без запятой между ними)
Список атрибутов
Раскрытия пакетов разрешены в списках атрибутов , если это допускается спецификацией атрибута. Например:
template<int... args> [[vendor::attr(args)...]] void* f();
` и `` оставлен без изменений, как и требовалось. HTML-разметка и атрибуты также сохранены в оригинальном виде.
Fold expressionsВ fold expressions , шаблоном является всё подвыражение, которое не содержит неразвёрнутый пакет. Using-declarationsВ using declarations , многоточие может появляться в списке деклараторов, что полезно при наследовании от пака параметров шаблона: template<typename... bases> struct X : bases... { using bases::g...; }; X<B, D> x; // OK: B::g and D::g introduced |
(since 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); Объявления друзейВ объявлениях друзей класса , за каждым спецификатором типа может следовать многоточие: 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; // классы C и E являются друзьями R<C, E> R<R<E>, R<C, int>> rr; // E::Nested и C являются друзьями R<R<E>, R<C, int>> Свернутые расширенные ограниченияВ свернутых расширенных ограничениях , шаблоном является ограничение этого свернутого расширенного ограничения. Свернутое расширенное ограничение не инстанцируется. |
(начиная с C++26) |
Примечания
|
Этот раздел не завершён
Причина: несколько слов о частичных специализациях и других способах доступа к отдельным элементам? Упомянуть рекурсию vs логарифмические подходы vs сокращения типа сверточных выражений |
| Макрос тестирования возможностей | Значение | Стандарт | Функция |
|---|---|---|---|
__cpp_variadic_templates
|
200704L
|
(C++11) | Вариативные шаблоны |
__cpp_pack_indexing
|
202311L
|
(C++26) | Индексация пакетов |
Пример
В приведенном ниже примере определяется функция, аналогичная
std::printf
, которая заменяет каждое вхождение символа
%
в строке формата на значение.
Первая перегрузка вызывается, когда передается только строка формата и не происходит раскрытия параметров.
Вторая перегрузка содержит отдельный параметр шаблона для головы аргументов и пакет параметров, это позволяет рекурсивному вызову передавать только хвост параметров, пока он не станет пустым.
Targs
— это пакет параметров шаблона, а
Fargs
— это пакет параметров функции.
#include <iostream> void tprintf(const char* format) // базовая функция { std::cout << format; } template<typename T, typename... Targs> void tprintf(const char* format, T value, Targs... Fargs) // рекурсивная вариативная функция { for (; *format != '\0'; format++) { if (*format == '%') { std::cout << value; tprintf(format + 1, Fargs...); // рекурсивный вызов return; } std::cout << *format; } } int main() { tprintf("% world% %\n", "Hello", '!', 123); }
Вывод:
Hello world! 123
Отчеты о дефектах
Следующие отчеты об изменениях в поведении, являющиеся дефектными, были применены ретроактивно к ранее опубликованным стандартам C++.
| DR | Применяется к | Поведение в опубликованной версии | Корректное поведение |
|---|---|---|---|
| CWG 1533 | C++11 | expansion пакета могла происходить в member initializer для члена класса | не разрешено |
| CWG 2717 | C++11 | instantiations of alignment specifiers были разделены запятыми | они разделяются пробелами |
Смотрите также
| Function template | Определяет семейство функций |
| Class template | Определяет семейство классов |
sizeof...
|
Запрашивает количество элементов в пакете |
| C-style variadic function | Принимает переменное количество аргументов |
| Preprocessor macros | Также могут быть вариативными |
| Fold expression | Сворачивает пакет с помощью бинарного оператора |
| Pack indexing | Обращается к элементу пакета по указанному индексу |