Параметры шаблона и аргументы шаблона
Содержание |
[править] Параметры шаблона
Каждый шаблон параметризуется одним или несколькими параметрами шаблона, указанными в списке-параметров синтаксиса объявления шаблона:
template < список-параметров > объявление
|
|||||||||
Каждый параметр в списке-параметров может быть:
- параметр шаблона, не являющийся типом;
- параметр шаблона тип;
- параметр шаблона шаблон.
[править] Параметр шаблона, не являющийся типом
тип имя (необязательно) | (1) | ||||||||
тип имя (необязательно) = default
|
(2) | ||||||||
тип ... имя (необязательно)
|
(3) | (начиная с C++11) | |||||||
заполнитель имя | (4) | (начиная с C++17) | |||||||
auto
(например, обычный auto, auto * или auto &), заполнитель для выведенного типа класса или (начиная с C++20) или decltype(auto).Параметр шаблона, не являющийся типом, должен иметь структурный тип, который является одним из следующих типов (необязательно cv-квалифицированный, квалификаторы игнорируются):
- тип ссылки lvalue (на объект или функцию);
- целочисленный тип;
- тип указателя (на объект или функцию);
- указатель на тип элемента (на объект-элемент или на функцию-элемент);
- тип перечисления;
(начиная с C++11) |
|
(начиная с C++20) |
Типы массивов и функций могут быть записаны в объявлении шаблона, но они автоматически заменяются указателем на объект и указателем на функцию по мере необходимости.
Когда имя параметра шаблона, не являющегося типом, используется в выражении в теле шаблона класса, оно является немодифицируемым prvalue, если только его тип не был ссылочным типом lvalue, или если его тип не является классовым типом (начиная с C++20).
Параметр шаблона вида class Foo не является безымянным нетиповым параметром шаблона типа Foo
, даже если иначе class Foo является уточнённым спецификатором типа и class Foo x; объявляет x
типа Foo
.
Тип параметра шаблона не типа может быть выведен, если он включает тип-заполнитель ( template<auto n> struct B { /* ... */ }; B<5> b1; // OK: тип параметра шаблона не типа есть int B<'a'> b2; // OK: тип параметра шаблона не типа есть char B<2.5> b3; // ошибка (до С++20): тип параметра шаблона не типа не может быть double // С++20 выводит заполнитель классового типа, аргументы шаблона класса // выводятся в месте вызова template<std::array arr> void f(); f<std::array<double, 8>{}>(); Для пакетов параметров шаблона не типов, в типе которых используется тип-заполнитель, тип выводится независимо для каждого аргумента шаблона и не обязательно должен совпадать: template<auto...> struct C {}; C<'C', 0, 2L, nullptr> x; // OK |
(начиная с C++17) |
Идентификатор, который именует параметр шаблона не типа типа класса struct A { friend bool operator==(const A&, const A&) = default; }; template<A a> void f() { &a; // OK const A& ra = a, &rb = a; // Оба связаны с одним и тем же объектом параметра шаблона assert(&ra == &rb); // проходит } |
(начиная с C++20) |
[править] Параметр шаблона тип
ключ-параметра-типа имя (необязательно) | (1) | ||||||||
ключ-параметра-типа имя (необязательно) = default
|
(2) | ||||||||
ключ-параметра-типа ... имя (необязательно)
|
(3) | (начиная с C++11) | |||||||
ограничение-типа имя (необязательно) | (4) | (начиная с C++20) | |||||||
ограничение-типа имя (необязательно) = default
|
(5) | (начиная с C++20) | |||||||
ограничение-типа ... имя (необязательно)
|
(6) | (начиная с C++20) | |||||||
ключ-параметра-типа | — | либо typename , либо class . Между этими ключевыми словами в объявлении параметра шаблона типа нет разницы
|
ограничение-типа | — | либо имя концепта, либо имя концепта, за которым следует список аргументов шаблона (в угловых скобках). В любом случае, имя концепта может быть дополнительно уточнено |
template<class T> class My_vector { /* ... */ };
template<class T = void> struct My_op_functor { /* ... */ };
template<My_concept T> class My_constrained_vector { /* ... */ };
template<My_concept T = void> class My_constrained_op_functor { /* ... */ };
template<My_concept... Ts> class My_constrained_tuple { /* ... */ };
Имя параметра необязательно:
// Объявления шаблонов, показанных выше: template<class> class My_vector; template<class = void> struct My_op_functor; template<typename...> class My_tuple;
В теле объявления шаблона имя параметра типа это typedef-имя, которое является псевдонимом типа, предоставляемого при создании экземпляра шаблона.
Каждый параметр с ограничениями
template<typename T> concept C1 = true; template<typename... Ts> // вариативный концепт concept C2 = true; template<typename T, typename U> concept C3 = true; template<C1 T> struct s1; // выражение-ограничения C1<T> template<C1... T> struct s2; // выражение-ограничения (C1<T> && ...) template<C2... T> struct s3; // выражение-ограничения (C2<T> && ...) template<C3<int> T> struct s4; // выражение-ограничения C3<T, int> template<C3<int>... T> struct s5; // выражение-ограничения (C3<T, int> && ...) |
(начиная с C++20) |
[править] Параметр шаблона шаблона
template < список-параметров > ключ-параметра-типа имя (необязательно)
|
(1) | ||||||||
template < список-параметров > ключ-параметра-типа имя (необязательно) = default
|
(2) | ||||||||
template < список-параметров > ключ-параметра-типа ... имя (необязательно)
|
(3) | (начиная с C++11) | |||||||
ключ-параметра-типа | — | class или typename (начиная с C++17)
|
В теле объявления шаблона имя этого параметра является именем шаблона (и требует создания экземпляров аргументов).
template<typename T> class my_array {}; // два параметра шаблона типа и один параметр шаблона шаблона: template<typename K, typename V, template<typename> typename C = my_array> class Map { C<K> key; C<V> value; };
[править] Разрешение имён для параметров шаблона
Имя параметра шаблона не может быть повторно объявлено в пределах его области видимости (включая вложенные области видимости). Параметр шаблона не может иметь то же имя, что и имя шаблона.
template<class T, int N> class Y { int T; // ошибка: параметр шаблона переопределён void f() { char T; // ошибка: параметр шаблона переопределён } }; template<class X> class X; // ошибка: параметр шаблона переопределён
В определении элемента шаблонного класса, которое появляется за пределами определения шаблоннного класса, имя элемента шаблона класса скрывает имя параметра шаблона любых включающих шаблонов класса, но не параметр шаблона элемента, если элемент является шаблоном класса или функции.
template<class T> struct A { struct B {}; typedef void C; void f(); template<class U> void g(U); }; template<class B> void A<B>::f() { B b; // B в A не параметр шаблона } template<class B> template<class C> void A<B>::g(C) { B b; // B в A не параметр шаблона C c; // параметр шаблона C, а не C в A }
В определении элемента шаблонного класса, которое появляется за пределами пространства имён, содержащего определение шаблонного класса, имя параметра шаблона скрывает имя элемента этого пространства имён.
namespace N { class C {}; template<class T> class B { void f(T); }; } template<class C> void N::B<C>::f(C) { C b; // C это параметр шаблона, а не N::C }
В определении шаблона класса или в определении элемента такого шаблона, который фигурирует вне определения шаблона, для каждого независимого базового класса, если имя базового класса или имя элемента базового класса совпадает с именем параметра шаблона, имя базового класса или имя элемента скрывает имя параметра шаблона.
struct A { struct B {}; int C; int Y; }; template<class B, class C> struct X : A { B b; // B в A C b; // ошибка: C в A это не имя типа };
[править] Аргументы шаблона
Для создания экземпляра шаблона каждый параметр шаблона (тип, не тип или шаблон) должен быть заменён соответствующим аргументом шаблона. Для шаблонов классов аргументы либо предоставляются явно , выведенными из инициализатора, (начиная с C++17) либо по умолчанию. Для шаблонов функций аргументы указываются явно, выводятся из контекста, или устанавливаются по умолчанию.
Если аргумент может быть интерпретирован как идентификатор типа, так и как выражение, он всегда интерпретируется как идентификатор типа, даже если соответствующий параметр шаблона не является типом:
template<class T> void f(); // #1 template<int I> void f(); // #2 void g() { f<int()>(); // "int()" это и тип, и выражение, // вызывает #1, потому что оно интерпретируется как тип }
[править] Аргументы шаблона не типы
Следующие ограничения применяются при создании экземпляров шаблонов с параметрами шаблона, отличными от типа:
В частности, это подразумевает, что строковые литералы, адреса элементов массива и адреса нестатических элементов не могут использоваться в качестве аргументов шаблона для создания экземпляров шаблонов, чьи соответствующие параметры шаблона не типы являются указателями на объекты. |
(до C++17) |
Аргумент шаблона, который можно использовать с параметром шаблона не типом, может ��ыть любым преобразованным константным выражением типа параметра шаблона. template<const int* pci> struct X {}; int ai[10]; X<ai> xi; // OK: преобразование массива в указатель и cv-квалификационное преобразование struct Y {}; template<const Y& b> struct Z {}; Y y; Z<y> z; // OK: нет преобразования template<int (&pa)[5]> struct W {}; int b[5]; W<b> w; // OK: нет преобразования void f(char); void f(int); template<void (*pf)(int)> struct A {}; A<&f> a; // OK: разрешение перегрузки выбирает f(int) Единственными исключениями являются параметры шаблона не типы типа ссылки или указателя и нестатические элементы данных типа ссылки или указателя в параметре шаблона не типа классового типа и его подобъекты (начиная с C++20) не могут ссылаться на /быть адресом
template<class T, const char* p> class X {}; X<int, "Studebaker"> x1; // ошибка: строковый литерал как аргумент шаблона template<int* p> class X {}; int a[10]; struct S { int m; static int s; } s; X<&a[2]> x3; // ошибка (до C++20): адрес элемента массива X<&s.m> x4; // ошибка (до C++20): адрес нестатического элемента X<&s.s> x5; // OK: адрес статического элемента X<&S::s> x6; // OK: адрес статического элемента template<const int& CRI> struct B {}; B<1> b2; // ошибка: для аргумента шаблона требуется временное значение int c = 1; B<c> b1; // OK |
(начиная с C++17) |
[править] Аргументы шаблона типы
Аргумент шаблона для параметра шаблона типа должен быть идентификатором-типа, который может именовать неполный тип:
template<typename T> class X {}; // шаблон класса struct A; // неполный тип typedef struct {} B; // псевдоним типа для безымянного типа int main() { X<A> x1; // OK: 'A' именует тип X<A*> x2; // OK: 'A*' именует тип X<B> x3; // OK: 'B' именует тип }
[править] Аргументы шаблона шаблона
Аргумент шаблона для параметра шаблона шаблона должен быть выражением-идентификатором, которое именует шаблон класса или псевдоним шаблона.
Если аргумент является шаблоном класса, при сопоставлении с параметром учитывается только основной шаблон. Частичные специализации, если таковые имеются, учитываются только тогда, когда специализация, основанная на этом параметре шаблона шаблона, создаётся.
template<typename T> // основной шаблон class A { int x; }; template<typename T> // частичная специализация class A<T*> { long x; }; // шаблон класса с параметром шаблона шаблона V template<template<typename> class V> class C { V<int> y; // использует основной шаблон V<int*> z; // использует частичную специализацию }; C<A> c; // c.y.x имеет тип int, c.z.x имеет тип long
Чтобы сопоставить аргумент шаблона шаблона A
с параметром шаблона шаблона P
, P
должен быть по крайней мере так же специализированным, как A
(смотрите ниже). Если список параметров P
включает пакет параметров, ноль или более параметров шаблона (или пакетов параметров) из списка параметров шаблона A
совпадают с ним. (начиная с C++11)
Формально параметр шаблона шаблона P
является по крайней мере так же специализированным, как и аргумент шаблона шаблона A
, если при следующей перезаписи двух шаблонов функций шаблон функции, соответствующий P
, не менее специализирован, чем шаблон функции, соответствующий A
, в соответствии с правилами частичного упорядочивания для шаблонов функций. Учитывая придуманный шаблон класса X
со списком параметров шаблона A
(включая аргументы по умолчанию):
- Каждый из двух шаблонов функций имеет одинаковые параметры шаблона, соответственно, как
P
илиA
. - Каждый шаблон функции имеет один параметр функци��, тип которого является специализацией
X
с аргументами шаблона, соответствующими параметрам шаблона из соответствующего шаблона функции, где для каждого параметра шаблонаPP
в списке параметров шаблона шаблона функции, формируется соответствующий аргумент шаблонаAA
. ЕслиPP
объявляет пакет параметров, тоAA
является расширением пакетаPP...
; иначе, (начиная с C++11)AA
является выражением-идентификаторомPP
.
Если в результате перезаписи получается недопустимый тип, то P
не является, по крайней мере, таким специализированным, как A
.
template<typename T> struct eval; // основной шаблон template<template<typename, typename...> class TT, typename T1, typename... Rest> struct eval<TT<T1, Rest...>> {}; // частичная специализация eval template<typename T1> struct A; template<typename T1, typename T2> struct B; template<int N> struct C; template<typename T1, int N> struct D; template<typename T1, typename T2, int N = 17> struct E; eval<A<int>> eA; // OK: соответствует частичной специализации eval eval<B<int, float>> eB; // OK: соответствует частичной специализации eval eval<C<17>> eC; // ошибка: C не соответствует TT в частичной специализации // потому что первый параметр TT является // параметром шаблона типом, а 17 не именует тип eval<D<int, 17>> eD; // ошибка: D не соответствует TT в частичной специализации // потому что второй параметр TT это пакет // параметров типа, а 17 не именует тип eval<E<int, float>> eE; // ошибка: E не соответствует TT в частичной специализации, // потому что третий параметр E (по умолчанию) не является типом
До принятия P0522R0 каждый из параметров шаблона A
должен точно соответствовать соответствующим параметрам шаблона P
. Это препятствует принятию многих разумных шаблонных аргументов.
Хотя на это было указано очень рано (CWG#150), к тому времени, когда оно было решено, изменения были внесены в рабочий документ C++17 и резолюция стала функциональностью де-факто C++17. Многие компиляторы отключают её по умолчанию:
- GCC по умолчанию отключает её во всех языковых режимах до C++17, её можно включить, только установив флаг компилятора в этих режимах.
- Clang по умолчанию отключает её во всех языковых режимах, её можно включить, только установив флаг компилятора.
- Microsoft Visual Studio рассматривает её как обычную функциональность C++17 и включает только в языковых режимах C++17 и более поздних версиях (т.е. не поддерживает языковой режим C++14, который является режимом по умолчанию).
template<class T> class A { /* ... */ }; template<class T, class U = T> class B { /* ... */ }; template<class... Types> class C { /* ... */ }; template<template<class> class P> class X { /* ... */ }; X<A> xa; // OK X<B> xb; // OK после P0522R0 // Ошибка ранее: не точное совпадение X<C> xc; // OK после P0522R0 // Ошибка ранее: не точное совпадение template<template<class...> class Q> class Y { /* ... */ }; Y<A> ya; // OK Y<B> yb; // OK Y<C> yc; // OK template<auto n> class D { /* ... */ }; // примечание: C++17 template<template<int> class R> class Z { /* ... */ }; Z<D> zd; // OK после P0522R0: параметр шаблона является // более специализированным, чем аргумент шаблона template<int> struct SI { /* ... */ }; template<template<auto> class> void FA(); // примечание: C++17 FA<SI>(); // Ошибка
[править] Аргументы шаблона по умолчанию
Аргументы шаблона по умолчанию указываются в списках параметров после знака =. Значения по умолчанию можно указать для любого типа параметра шаблона (типа, не типа или шаблона), но не для пакетов параметров (начиная с C++11).
Если значение по умолчанию указано для параметра шаблона шаблона основного класса , шаблона первичной переменной, (начиная с C++14) или псевдонима шаблона, каждый последующий параметр шаблона должен иметь аргумент по умолчанию, за исключением того, что последний может быть пакетом параметров шаблона (начиная с C++11). В шаблоне функции нет ограничений на параметры, которые следуют за значением по умолчанию, а за пакетом параметров могут следовать дополнительные параметры типа, только если они имеют значения по умолчанию или могут быть выведены из аргументов функции (начиная с C++11).
Параметры по умолчанию не разрешены
- во внеклассовом определении элемента шаблона класса (они должны быть указаны в объявлении внутри тела класса). Обратите внимание, что шаблоны-элементы классов, не являющихся шаблонами, могут использовать параметры по умолчанию в своих определениях вне класса (смотрите Ошибка GCC 53856)
- в объявлениях шаблонов дружественных классов
|
(до C++11) |
В объявлении шаблона дружественной функции аргументы шаблона по умолчанию разрешены только в том случае, если объявление является определением, и никакие другие объявления этой функции не появляются в этой единице трансляции. |
(начиная с C++11) |
Аргументы шаблона по умолчанию, которые появляются в объявлениях, объединяются аналогично аргументам функции по умолчанию:
template<typename T1, typename T2 = int> class A; template<typename T1 = int, typename T2> class A; // вышеприведённое аналогично следующему: template<typename T1 = int, typename T2 = int> class A;
Но одному и тому же параметру нельзя дважды задавать аргументы по умолчанию в одной и той же области видимости:
template<typename T = int> class X; template<typename T = int> class X {}; // ошибка
При синтаксическом анализе аргумента шаблона по умолчанию для параметра шаблона не типа, первый не вложенный > берётся как конец списка параметров шаблона, а не как оператор больше-чем:
template<int i = 3 > 4> // ошибка синтаксиса class X { /* ... */ }; template<int i = (3 > 4)> // OK class Y { /* ... */ };
Списки параметров шаблона параметров шаблона шаблона могут иметь свои собственные аргументы по умолчанию, которые действуют только в том случае, если сам параметр шаблона шаблона находится в области видимости:
// шаблон класса с параметром шаблона типа со значением по умолчанию template<typename T = float> struct B {}; // шаблон шаблона шаблона T имеет список параметров, который состоит // из одного параметра шаблона типа со значением по умолчанию template<template<typename = float> typename T> struct A { void f(); void g(); }; // определения шаблонов функций-элементов вне тела template<template<typename TT> class T> void A<T>::f() { T<> t; // ошибка: TT не имеет значения по умолчанию в области видимости } template<template<typename TT = char> class T> void A<T>::g() { T<> t; // OK: t есть T<char> }
Доступ к элементам для имён, используемых в параметре шаблона по умолчанию, проверяется в объявлении, а не в момент использования:
class B {}; template<typename T> class C { protected: typedef T TT; }; template<typename U, typename V = typename U::TT> class D: public U {}; D<C<B>>* d; // ошибка: C::TT защищён
Аргумент шаблона по умолчанию создаётся неявно, когда требуется значение этого аргумента по умолчанию, за исключением случаев, когда шаблон используется для именования функции: template<typename T, typename U = int> struct S {}; S<bool>* p; // Аргумент по умолчанию для U создаётся в этот момент // тип p есть S<bool, int>* |
(начиная с C++14) |
[править] Эквивалентность аргументов шаблона
Эквивалентность аргументов шаблона используется для определения того, являются ли два идентификатора шаблона одинаковыми.
Два значения эквивалентные-аргументы-шаблона, если они одного типа и
- они имеют целочисленный тип или тип перечисления, и их значения одинаковы
- или они имеют тип указателя и имеют одинаковое значение указателей
- или они имеют тип указателя на элемпент, и они ссылаются на один и тот же элемент класса, или оба являются нулевым значением указателя на элемент
- или они имеют ссылочный тип lvalue и ссылаются на один и тот же объект или функцию
|
(начиная с C++11) |
|
(начиная с C++20) |
[править] Примечание
Макрос тест функциональности | Значение | Стандарт | Комментарий |
---|---|---|---|
__cpp_nontype_template_parameter_auto |
201606L | (C++17) | Объявление параметра шаблона, не являющегося типом с auto
|
__cpp_template_template_args |
201611L | (c++17) | Соответствие аргументов шаблона шаблона |
__cpp_nontype_template_args |
201411L | (C++17) | Разрешить константное вычисление для всех аргументов шаблона не типов |
201911L | (C++20) | Классовые типы и типы с плавающей запятой в параметре шаблона, не являющегося типом |
[править] Пример
#include <array> #include <iostream> #include <numeric> // простой параметр шаблона, не являющийся типом template<int N> struct S { int a[N]; }; template<const char*> struct S2 {}; // сложный нетиповой пример template < char c, // целочисленный тип int (&ra)[5], // ссылка lvalue на объект (типа массива) int (*pf)(int), // указатель на функцию int (S<10>::*a)[10] // указатель на объект-элемент (типа int[10]) > struct Complicated { // вызывает функцию, выбранную во время компиляции // и сохраняет результат в массиве, выбранном во время компиляции void foo(char base) { ra[4] = pf(c - base); } }; // S2<"fail"> s2; // ошибка: нельзя использовать строковый литерал char okay[] = "okay"; // статический объект со связыванием // S2<&okay[0]> s3; // ошибка: элемент массива не имеет связывания S2<okay> s4; // работает int a[5]; int f(int n) { return n; } // C++20: NTTP может быть литеральным классовым типом template<std::array arr> constexpr auto sum() { return std::accumulate(arr.cbegin(), arr.cend(), 0); } // C++20: аргументы шаблона класса выводятся в месте вызова static_assert(sum<std::array<double, 8>{3, 1, 4, 1, 5, 9, 2, 6}>() == 31.0); // C++20: Вывод аргументов NTTP и CTAD static_assert(sum<std::array{2, 7, 1, 8, 2, 8}>() == 28); int main() { S<10> s; // s.a представляет собой массив из 10 int s.a[9] = 4; Complicated<'2', a, f, &S<10>::a> c; c.foo('0'); std::cout << s.a[9] << a[4] << '\n'; }
Вывод:
42
Этот раздел не завершён Причина: больше примеров |
[править] Отчёты о дефектах
Следующие изменения поведения были применены с обратной силой к ранее опубликованным стандартам C++:
Номер | Применён | Поведение в стандарте | Корректное поведение |
---|---|---|---|
CWG 150 | C++98 | аргументы шаблона-шаблона должны точно соответствовать спискам параметров шаблона-шаблона |
разрешены и более специализированные (решено P0522R0) |
CWG 184 | C++98 | разрешено ли параметрам шаблона параметров шаблона шаблона иметь аргументы по умолчанию, не указано |
спецификация добавлена |
CWG 354 | C++98 | значения нулевого указателя не могут быть аргументами шаблона, отличными от типа |
позволено |
CWG 1398 | C++11 | нетиповые аргументы шаблона не могут иметь типstd::nullptr_t
|
позволено |
CWG 1570 | C++98 | нетиповые аргументы шаблона могут обозначать адреса подобъектов |
запрещено |
CWG 1922 | C++98 | было неясно, может ли шаблон класса, имя которого является внедрённым именем класса, использовать аргументы по умолчанию в предыдущих объявлениях |
позволено |
CWG 2032 | C++14 | для переменных шаблонов не было ограничения на параметры шаблона после параметра шаблона с аргументом по умолчанию |
применено то же ограничение, что и для шаблонов классов и шаблонов псевдонимов |