Пакет параметров(начиная с C++11)
Пакет параметров шаблона это параметр шаблона, который принимает ноль или более аргументов шаблона (не типов, типов или шаблонов). Пакет параметров функции это параметр функции, который принимает ноль или более аргументов функции.
Шаблон хотя бы с одним пакетом параметров называется вариативным шаблоном.
[править] Синтаксис
Пакет параметров шаблона (появляется в списке параметров шаблона псевдонима, шаблона класса ,шаблона переменной (начиная с C++14) и шаблона функции)
тип ... имя-пакета (необязательно)
|
(1) | ||||||||
typename | class ... имя-пакета (необязательно)
|
(2) | ||||||||
ограниечение-типа ... имя-пакета (необязательно)
|
(3) | (начиная с C++20) | |||||||
template < список-параметров > class ... имя-пакета (необязательно)
|
(4) | (до C++17) | |||||||
template < список-параметров > typename | class ... имя-пакета (необязательно)
|
(4) | (начиная с C++17) | |||||||
Пакет параметров функции (в форме декларатора, появляется в списке параметров функции вариативного шаблона функции)
имя-пакета ... имя-пакета-параметров (необязательно)
|
(5) | ||||||||
Расширение пакета параметров (появляется в теле вариативного шаблона)
образец ...
|
(6) | ||||||||
3) Пакет параметров шаблона ограниченного типа с необязательным именем
|
(начиная с C++20) |
образцов
. Образец должен включать как минимум один пакет параметров.[править] Объяснение
Вариативный шаблон класса может быть создан с любым количеством аргументов шаблона:
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) }
[править] Расширение местоположений
В зависимости от того, где происходит расширение, результирующий список, разделённый запятыми, представляет собой список различных типов: список параметров функции, список инициализаторов элементов, список атрибутов и т.д. Ниже приводится список всех разрешенных контекстов.
[править] Списки аргументов функции
Расширение пакета может появиться внутри круглых скобок оператора вызова функции, и в этом случае наибольшее выражение или список-инициализации в фигурных скобках слева от многоточия является образцом, который раскрывается.
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)... }; }
[править] Списки аргументов шаблона
Расширения пакета можно использовать в любом месте списка аргументов шаблона при условии, что у шаблона есть параметры, соответствующие расширению.
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...) { // ... бросать разные X-ы в разных ситуациях } |
(до C++17) |
[править] Спецификатор выравнивания
Расширения пакетов разрешены как в списках типов, так и в списках выражений, используемых ключевым словом alignas
[править] Список атрибутов
Расширения пакета разрешены в списках атрибутов, если это разрешено спецификацией атрибута. Например:
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 и D::g |
(начиная с C++17) |
[править] Примечание
Этот раздел не завершён Причина: несколько слов о частичных специализациях и других способах доступа к отдельным элементам? Упоминание рекурсии против логарифмических операций против сокращений, таких как выражения свертки |
Макрос Тестирования функциональности | Значение | Стандарт | Функциональность |
---|---|---|---|
__cpp_variadic_templates |
200704L | (C++11) | Вариативные шаблоны |
[править] Пример
В приведённом ниже примере определяется функция, аналогичная 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("% мир% %\n","Привет",'!',123); return 0; }
Вывод:
Привет мир! 123
[править] Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 1533 | C++11 | расширение пакета может произойти в инициализаторе элемента для элемента | не допускается |
[править] Смотрите также
шаблон функции | |
шаблон класса | |
sizeof...
|
Запрашивает количество элементов в пакете параметров. |
Вариативные функции в стиле C | |
Макросы препроцессора | Также могут быть вариативными |
Выражения свёртки |