Spec-Zone.ru › C++

Упакованный параметр (с C++11)

Упакованный параметр шаблона — это параметр шаблона, который принимает ноль или более аргументов шаблона (нетипов, типов или шаблонов). Упакованный параметр функции — это параметр функции, который принимает ноль или более аргументов функции.

Шаблон с по крайней мере одним упакованным параметром называется шаблоном с переменным числом аргументов.

Синтаксис

Упакованный параметр шаблона (встречается в шаблоне псевдонима типа, шаблоне класса, шаблоне переменной(с C++14) и шаблоне функции списка параметров)

тип ... имя_пакета (необязательно) (1)
typename|class ... имя_пакета (необязательно) (2)
ограничение_типа ... имя_пакета (необязательно) (3) (с C++20)
template < список_параметров > class ... имя_пакета (необязательно) (4) (до C++17)
template < список_параметров > typename|class ... имя_пакета (необязательно) (4) (с C++17)

Упакованный параметр функции (форма декларатора, встречается в списке параметров функции шаблонной функции с переменным числом аргументов)

имя_пакета ... имя_параметра_пакета (необязательно) (5)

Расширение упакованного параметра (встречается в теле шаблона с переменным числом аргументов)

шаблон ... (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 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

Spec-Zone.ru

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