Spec-Zone.ru › C++

Пакет (начиная с 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)
1) Пакет нетиповых параметров шаблона с необязательным именем
2) Пакет параметров типа шаблона с необязательным именем
3) Ограниченный пакет параметров типа шаблона с необязательным именем (начиная с C++20)
4) Пакет параметров шаблона-шаблона с необязательным именем
5) Пакет параметров функции с необязательным именем
6) Раскрытие пакета: раскрывается в список из нуля или более 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

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API