Namespaces
Variants

Pack (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Template specialization
Parameter packs (C++11)
Miscellaneous

Пакет — это сущность 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)
1) Постоянный пакет параметров шаблона с необязательным именем
2) Параметр шаблона типа с переменным числом аргументов с необязательным именем
3) Пакет параметров шаблона типа с ограничениями с необязательным именем
(since C++20)
4) Шаблонный параметр-пакет шаблонов с необязательным именем
5) Параметр функции с пакетом параметров с опциональным именем
6) Распаковка пакета: раскрывается в список из нуля или более 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();
**Примечание:** Весь код C++ внутри тегов `
` и `` оставлен без изменений, как и требовалось. 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)

Примечания

Макрос тестирования возможностей Значение Стандарт Функция
__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 Обращается к элементу пакета по указанному индексу