Parámetros de plantilla y argumentos de plantilla
Contenido |
[editar] Parámetros de plantilla
Cada plantilla está parametrizada por uno o más parámetros de plantilla, indicado en la lista-de-parámetros de la sintaxis de declaración de plantilla:
template < lista-de-parámetros > declaración
|
|||||||||
Cada parámetro en la lista-de-parámetros puede ser:
- un parámetro de plantilla de no tipo;
- un parámetro de plantilla de tipo;
- un parámetro de plantilla de plantilla.
[editar] Parámetro de plantilla de no tipo
tipo nombre(opcional) | (1) | ||||||||
tipo nombre(opcional) = default
|
(2) | ||||||||
tipo ... nombre(opcional)
|
(3) | (desde C++11) | |||||||
marcador-de-posición nombre | (4) | (desde C++17) | |||||||
auto
(tales como el auto sencillo, auto ** o auto &), un marcador de posición para un tipo clase deducido (desde C++20), o decltype(auto).Un parámetro de plantilla de no tipo debe tener un tipo estructural, que es uno de los siguientes tipos (opcionalmente calificado-cv, los calificadores se ignoran):
- un tipo referencia lvalue (a un objeto o a una función);
- un tipo entero;
- un tipo puntero (a un objeto o a una función);
- un tipo puntero a miembro (a un objeto miembro o a una función miembro);
- un tipo enumeración;
(desde C++11) |
|
(desde C++20) |
Pueden escribirse los tipos array y función en la declaración de la plantilla, pero se reemplazan automáticamente por punteros a objeto y punteros a función, según lo apropiado.
Cuando el nombre de un parámetro de plantilla de no tipo se utiliza en una expresión dentro del cuerpo de la plantilla de clase, es un prvalue inmodificable, a menos que su tipo fuera un tipo referencia lvalue, o a menos que su tipo sea un tipo clase (desde C++20).
Un parámetro de plantilla de la forma class Foo no es un parámetro de plantilla de no tipo no denominado de tipo Foo
, aun si class Foo es un especificador de tipo elaborado y class Foo x; declara que x
sea de tipo Foo
.
El tipo de un parámetro de plantilla de no tipo puede deducirse si incluye un tipo marcador de posición ( template<auto n> struct B { /* ... */ }; B<5> b1; // de acuerdo: el tipo del parámetro de plantilla de no tipo es int B<'a'> b2; // de acuerdo: el tipo del parámetro de plantilla de no tipo es char B<2.5> b3; // ERROR (hasta C++20): el tipo del parámetro de plantilla de no tipo no puede // ser double // Marcador de posición de tipo de clase deducida C++20, los argumentos de la plantilla de // clase se deducen en el lugar de la llamada template<std::array arr> void f(); f<std::array<double, 8>{}>(); Para los paquetes de parámetros de plantilla de no tipo cuyos tipos utilizan un tipo marcador de posición, se deduce el tipo independientemente para cada argumento de plantilla y no necesitan coincidir: template<auto...> struct C {}; C<'C', 0, 2L, nullptr> x; // de acuerdo |
(desde C++17) |
Un identificador que denomina a un parámetro de plantilla de no tipo de tipo clase struct A { friend bool operator==(const A&, const A&) = default; }; template<A a> void f() { &a; // de acuerdo const A& ra = a, &rb = a; // Ambos están vinculados al mismo objeto de parámetro // de plantilla assert(&ra == &rb); // pasa } |
(desde C++20) |
[editar] Parámetro de plantilla de tipo
clave-de-parámetro-de-tipo nombre(opcional) | (1) | ||||||||
clave-de-parámetro-de-tipo nombre(opcional) = default
|
(2) | ||||||||
clave-de-parámetro-de-tipo ... nombre(opcional)
|
(3) | (desde C++11) | |||||||
restricción-de-tipo nombre(opcional) | (4) | (desde C++20) | |||||||
restricción-de-tipo nombre(opcional) = default
|
(5) | (desde C++20) | |||||||
restricción-de-tipo ... nombre(opcional)
|
(6) | (desde C++20) | |||||||
typename
o class
. No existe diferencia entre estas palabras clave en una declaración de parámetro de plantilla de tipo.
restricción-de-tipo | - | Ya sea el nombre de un concepto o el nombre de un concepto seguido de una lista de argumentos de plantilla (entre corchetes angulares). De cualquier forma, el nombre del concepto opcionalmente puede calificarse. |
template<class T> class Mi_vector { /* ... */ };
template<class T = void> struct Mi_op_functor { /* ... */ };
template<typename... Ts> class Mi_tupla { /* ... */ };
template<Mi_concepto T> class Mi_vector_restringido { /* ... */ };
template<Mi_concepto T = void> class Mi_op_functor_restringido { /* ... */ };
template<Mi_concepto... Ts> class Mi_tupla_restringida { /* ... */ };
El nombre del parámetro es opcional:
// Declaraciones de las plantillas mostradas arriba: template<class> class Mi_vector; template<class = void> struct Mi_op_functor; template<typename...> class Mi_tupla;
En el cuerpo de la declaración de plantilla, el nombre de un parámetro de tipo es un nombre typedef
que es un alias del tipo proporcionado cuando se instancia la plantilla.
Cada parámetro restringido
template<typename T> concept C1 = true; template<typename... Ts> // concepto variádico concept C2 = true; template<typename T, typename U> concept C3 = true; template<C1 T> struct s1; // la expresión de restricción es C1<T> template<C1... T> struct s2; // la expresión de restricción es (C1<T> && ...) template<C2... T> struct s3; // la expresión de restricción es (C2<T> && ...) template<C3<int> T> struct s4; // la expresión de restricción es C3<T, int> template<C3<int>... T> struct s5; // la expresión de restricción es (C3<T, int> && ...) |
(desde C++20) |
[editar] Parámetro de plantilla de plantilla
template < lista-de-parámetros > clave-de-parámetro-de-tipo nombre(opcional)
|
(1) | ||||||||
template < lista-de-parámetros > clave-de-parámetro-de-tipo nombre(opcional) = default
|
(2) | ||||||||
template < lista-de-parámetros > clave-de-parámetro-de-tipo ... nombre(opcional)
|
(3) | (desde C++11) | |||||||
clave-de-parámetro-de-tipo | - | class o typename (desde C++17)
|
En el cuerpo de la declaración de la plantilla, el nombre de este parámetro es un nombre-de-plantilla (y necesita que se instancien los argumentos).
template<typename T> class mi_array {}; // dos parámetros de plantilla de tipo y un parámetro de plantilla de plantilla: template<typename K, typename V, template<typename> typename C = mi_array> class Mapa { C<K> clave; C<V> valor; };
[editar] Resolución de nombres para parámetros de plantilla
No se permite que se redeclare el nombre de un parámetro de plantilla dentro de su ámbito (incluyendo ámbitos anidados). No se permite que un parámetro de plantilla tenga el mismo nombre que el nombre de la plantilla.
template<class T, int N> class Y { int T; // ERROR: parámetro de plantilla redeclarado void f() { char T; // ERROR: parámetro de plantilla redeclarado } }; template<class X> class X; // ERROR: parámetro de plantilla redeclarado
En la definición de un miembro de una plantilla de clase fuera de la definición de la plantilla de clase, el nombre de un miembro de la plantilla de clase oculta el nombre de un parámetro de plantilla de cualquier plantilla de clase circundante, pero no un parámetro de plantilla del miembro si el miembro es una plantilla de clase o plantilla de función.
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; // la B de A, no el parámetro de plantilla } template<class B> template<class C> void A<B>::g(C) { B b; // la B de A, no el parámetro de plantilla C c; // el parámetro de plantilla C, no la C de A }
En la definición de un miembro de una plantilla de clase que aparece fuera del espacio de nombres que contiene la definición de la plantilla de clase, el nombre de un parámetro de plantilla oculta el nombre de un miembro de este espacio de nombres.
namespace N { class C {}; template<class T> class B { void f(T); }; } template<class C> void N::B<C>::f(C) { C b; // C es el parámetro de plantilla, no N::C }
En la definición de una plantilla de clase o en la definición de un miembro de tal plantilla que aparece fuera de la definición de la plantilla, para cada clase base no dependiente, si el nombre de la clase base o el nombre de un miembro de la clase base es el mismo que el nombre de un parámetro de plantilla, el nombre de la clase base o el nombre del miembro oculta el nombre del parámetro de plantilla.
struct A { struct B {}; int C; int Y; }; template<class B, class C> struct X : A { B b; // la B de A C b; // ERROR: la C de A no es un nombre de tipo. };
[editar] Argumentos de plantilla
Para que una plantilla pueda instanciarse, cada parámetro de plantilla (de tipo, de no tipo, o de plantilla) debe reemplazarse por un argumento de plantilla correspondiente. Para las plantillas de clase, los argumentos son o bien proporcionados explícitamente, deducidos del inicializador, (desde C++17) o por defecto. Para las plantillas de función, los argumentos se proporcionan explícitamente, deducidos del contexto, o por defecto.
Si un argumento puede interpretarse tanto como un id-de-tipo como una expresión, siempre se interpreta como un id-de-tipo, incluso si el parámetro de plantilla correspondiente es de no tipo:
template<class T> void f(); // #1 template<int I> void f(); // #2 void g() { f<int()>(); // "int()" es tanto un tipo como una expresión // llama a #1 porque se interpreta como un tipo }
[editar] Argumentos de plantilla de no tipo
Se aplican las siguientes limitaciones al instanciar plantillas que tengan par��metros de plantilla de no tipo:
En particular, esto implica que los literales de cadena, las direcciones a elementos de arrays, y direcciones de miembros no estáticos no pueden usarse como argumentos de plantilla para instanciar plantillas cuyos parámetros de plantilla de no tipo correspondientes son punteros a objetos. |
(hasta C++17) |
El argumento de plantilla que puede usarse con un parámetro de plantilla de no tipo puede ser cualquier expresión constante convertida del tipo del parámetro de plantilla. template<const int* pci> struct X {}; int ai[10]; X<ai> xi; // de acuerdo: conversión de array a puntero y conversión de calificación-cv struct Y {}; template<const Y& b> struct Z {}; Y y; Z<y> z; // de acuerdo: no hay conversión template<int (&pa)[5]> struct W {}; int b[5]; W<b> w; // de acuerdo: no hay conversión void f(char); void f(int); template<void (*pf)(int)> struct A {}; A<&f> a; // de acuerdo: la resolución de sobrecarga selecciona a f(int) Las únicas excepciones son que los parámetros de no tipo de tipo referencia o puntero y los datos miembro no estáticos de tipo referencia o puntero en un parámetro de plantilla de no tipo de tipo clase y sus subobjetos (desde C++20) no pueden referirse a, o ser la dirección de
template<class T, const char* p> class X {}; X<int, "Saavedra"> x1; // ERROR: literal de cadena como argumento de plantilla template<int* p> class X {}; int a[10]; struct S { int m; static int s; } s; X<&a[2]> x3; // ERROR (hasta C++20): dirección de elemento de array X<&s.m> x4; // ERROR (hasta C++20): dirección de miembro no estático X<&s.s> x5; // de acuerdo: dirección de miembro estático X<&S::s> x6; // de acuerdo: dirección de miembro estático template<const int& CRI> s truct B {}; B<1> b2; // ERROR: se requeriría temporal para argumento de plantilla int c = 1; B<c> b1; // de acuerdo |
(desde C++17) |
[editar] Argumentos de plantilla de tipo
Un argumento de plantilla para un parámetro de plantilla de tipo debe ser un id-de-tipo, que puede denominar un tipo incompleto:
template<typename T> class X {}; // plantilla de clase struct A; // tipo incompleto typedef struct {} B; // alias de tipo a un tipo sin denominar int main() { X<A> x1; // de acuerdo: 'A' denomina un tipo X<A*> x2; // de acuerdo: 'A*' denomina un tipo X<B> x3; // de acuerdo: 'B' denomina un tipo }
[editar] Argumentos de plantilla de plantilla
Un argumento de plantilla para un parámetro de plantilla de plantilla debe ser una expresión-id que denomine a una plantilla de clase o a un alias de plantilla.
Cuando el argumento es una plantilla de clase, solamente se considera la plantilla primaria cuando coincide con el parámetro. Se consideran las especializaciones parciales, si es que las hay, solamente cuando sucede que una especialización basada en este parámetro de plantilla de plantilla es instanciada.
template<typename T> class A { int x; }; // plantilla primaria template<class T> class A<T*> { long x; }; // especialización parcial // plantilla de clase con un parámetro de plantilla de plantilla V template<template<typename> class V> class C { V<int> y; // usa la plantilla primaria V<int*> z; // usa la especialización parcial }; C<A> c; // c.y.x tiene tipo int, c.z.x tiene tipo long
Para coincidir un argumento de plantilla de plantilla A
con el parámetro de plantilla de plantilla P
, P
debe estar especializado al menos tanto como A
(ver más abajo). Si la lista de parámetros de P
incluye un paquete de parámetros, cero o más parámetros de plantilla (o paquetes de parámetros) de la lista de parámetros de plantilla de A
se corresponden con él. (desde C++11)
Formalmente, un parámetro de plantilla de plantilla P
es al menos tan especializado como un argumento de plantilla de plantilla A
si, dada la siguiente reescritura en dos plantillas de función, la plantilla de función correspondiente a P
es al menos tan especializada como la plantilla de función correspondiente a A
de acuerdo con las reglas de ordenamiento parcial para plantillas de función. Dada una plantilla de clase inventada X
con la lista de parámetros de plantilla A
(incluyendo los argumentos por defecto):
- Cada una de las dos plantillas de funciones tiene los mismos parámetros de plantilla, respectivamente, que
P
oA
. - Cada plantilla de función tiene un único parámetro de función cuyo tipo es una especialización de
X
con argumentos de plantilla correspondientes a los parámetros de plantilla de la respectiva plantilla de función donde, para cada parámetro de plantillaPP
en la lista de parámetros de plantilla de la plantilla de función, un argumento de plantilla correspondienteAA
se forma. SiPP
declara un paquete de parámetros, entoncesAA
es la expansión del paquetePP...
; de lo contrario, (desde C++11)AA
es la expresión-idPP
.
Si la reescritura produce un tipo inválido, entonces P
no es al menos tan especializado como A
.
template<typename T> struct eval; // plantilla primaria template<template<typename, typename...> class TT, typename T1, typename... Rest> struct eval<TT<T1, Rest...>> {}; // especialización parcial de 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; // de acuerdo: coincide la especialización parcial de eval eval<B<int, float>> eB; // de acuerdo: coincide la especialización parcial de eval eval<C<17>> eC; // ERROR: C no coincide TT en la especialización parcial porque // el primer parámetro de TT es un parámetro de plantilla de tipo, // mientras que 17 no denomina un tipo eval<D<int, 17>> eD; // ERROR: D no coincide con TT en la especialización parcial // porque el segundo parámetro de TT es un paquete de parámetros de // tipo, mientras que 17 no denomina un tipo eval<E<int, float>> eE; // ERROR: E no coincide con TT en la especialización parcial // porque el tercer parámetro (por defecto) de E es de no tipo
Antes de la adopción de P0522R0, cada uno de los parámetros de plantilla de A
debe coincidir exactamente con los parámetros de plantilla de P
. Esto impide la aceptación de muchos argumentos de plantilla razonables.
Aunque se señalo muy temprano (CWG#150), cuando se resolvió, los cambios se aplicaron al documentos de trabajo de C++17 y la resolución se convirtió en una característica de facto de C++17. Muchos compiladores lo deshabilitan por defecto:
- GCC lo deshabilita en todos los modos del lenguaje anteriores a C++17 de forma predeterminada, solo se puede habilitar configurando un flag de compilador en estos modos.
- Clang lo deshabilita en todos los modos del lenguaje de forma predeterminada, solo se puede habilitar configurando un flag de compilador.
- Microsoft Visual Studio lo trata como una característica normal de C++17 y solo lo habilita en C++17 y posteriores (es decir, no soportado en el modo de lenguaje C++14, que es el modo predeterminado).
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; // de acuerdo X<B> xb; // de acuerdo después de P0522R0 // ERROR previamente: no es una coincidencia exacta X<C> xc; // de acuerdo después de P0522R0 // ERROR previamente: no es una coincidencia exacta template<template<class ...> class Q> class Y { /* ... */ }; Y<A> ya; // de acuerdo Y<B> yb; // de acuerdo Y<C> yc; // de acuerdo template<auto n> class D { /* ... */ }; // observación: C++17 template<template<int> class R> class Z { /* ... */ }; Z<D> zd; // de acuerdo después de P0522R0: el parámetro de plantilla // es más especializado que el argumento de plantilla template <int> struct SI { /* ... */ }; template <template <auto> class> void FA(); // observación: C++17 FA<SI>(); // ERROR
[editar] Argumentos de plantilla por defecto
Los argumentos de plantilla por defecto se especifican en las listas de parámetros después del signo =. Pueden especificarse valores por defecto para cualquier tipo de parámetro de plantilla (de tipo, de no tipo, o de plantilla), pero no para los paquetes de parámetros (desde C++11).
Si se especifica el valor por defecto para un parámetro de plantilla de una plantilla de clase primaria, plantilla de variable primaria, (desde C++14) o plantilla de alias, cada parámetro de plantilla subsecuente debe tener un argumento por defecto, excepto que el último puede ser un paquete de parámetros de plantilla (desde C++11). En una plantilla de función, no existen restricciones en los parámetros que puedan seguir a un valor por defecto, y un paquete de parámetros puede ser seguido de más parámetros de tipo solamente si tienen valores por defecto o pueden deducirse de los argumentos de función (desde C++11).
No se permiten los parámetros por defecto
- en la definición fuera de la clase de un miembro de una plantilla de clase (tienen que proporcionarse en la declaración dentro del cuerpo de la clase). Observa que los miembros de plantilla de clases no-plantilla pueden usar parámetros por defecto en sus definiciones fuera de la clase (véase defecto GCC 53856);
- en las declaraciones de plantillas de clase amigas;
|
(hasta C++11) |
En una declaración de una plantilla de función amiga, se permiten los argumentos de plantilla por defecto solamente si la declaración es una definición, y no aparece ninguna otra declaración en esta unidad de traducción. |
(desde C++11) |
Los argumentos de plantilla por defecto que aparecen en las declaraciones se combinan de manera similar a los argumentos de función por defecto:
template<typename T1, typename T2 = int> class A; template<typename T1 = int, typename T2> class A; // lo anterior es lo mismo que lo siguiente: template<typename T1 = int, typename T2 = int> class A;
Pero no se le pueden dar argumentos por defecto dos veces al mismo parámetro en el mismo ámbito
template<typename T = int> class X; template<typename T = int> class X {}; // ERROR
Al analizar un argumento de plantilla predeterminado para un parámetro de plantilla que no es de tipo, el primer > no anidado se toma como el final de la lista de parámetros de plantilla en lugar de un operador “mayor que”:
template<int i = 3 > 4> // error de sintaxis class X { /* ... */ }; template<int i = (3 > 4)> // Correcto class Y { /* ... */ };
Las listas de parámetros de plantilla de los parámetros de plantilla de plantilla pueden tener sus propios argumentos por defecto, que solamente tienen efecto donde el parámetro de plantilla de plantilla mismo se encuentra en ámbito:
// plantilla de clase, con un parámetro de plantilla de tipo con un valor por defecto template<typename T = float> struct B {}; // el parámetro de plantilla de plantilla T tiene una lista de parámetros, que // consiste de un parámetro de plantilla de tipo con un valor por defecto template<template<typename = float> typename T> struct A { void f(); void g(); }; // definiciones de plantillas de función miembro fuera del cuerpo de la clase template<template<typename TT> class T> void A<T>::f() { T<> t; // ERROR: TT no tiene valor por defecto en el ámbito } template<template<typename TT = char> class T> void A<T>::g() { T<> t; // de acuerdo: t es T<char> }
El acceso a miembros para los nombres usados en un parámetro de plantilla por defecto se comprueban en la declaración, no en el punto de uso:
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; // ERROR: C::TT está protegida
El argumento de plantilla por defecto se instancia implícitamente cuando el valor de ese argumento por defecto se necesita, excepto si la plantilla se usa para denominar a una función: template<typename T, typename U = int> struct S { }; S<bool>* p; // El argumento por defecto para U se instancia en este punto // el tipo de p es S<bool, int>* |
(desde C++14) |
[editar] Equivalencia de argumentos de plantilla
La equivalencia de argumentos de plantilla se utiliza para determinar si dos id-de-plantilla son las mismas.
Dos valores son argumentos de plantilla equivalentes si son del mismo tipo y
- son de tipo entero o enumeración y sus valores son los mismos;
- o son de tipo puntero y tienen el mismo valor de puntero;
- o son de tipo puntero a miembro y se refieren al mismo miembro de clase o ambos son el valor del puntero a miembro nulo;
- o son de tipo referencia lvalue y se refieren al mismo objeto o función;
|
(desde C++11) |
|
(desde C++20) |
[editar] Notas
Prueba de característica | Valor | Estándar | Comentario |
---|---|---|---|
__cpp_nontype_template_parameter_auto |
201606L | (C++17) | Declaración de parámetros de plantilla de no-tipo con auto
|
__cpp_template_template_args |
201611L | (c++17) | Coincidencia de argumentos de plantilla de plantilla |
__cpp_nontype_template_args |
201411L | (C++17) | Permitir la evaluación constante para todos los argumentos de plantilla de no-tipo |
201911L | (C++20) | Tipos de clase y de punto flotante en parámetros de plantilla de no-tipo |
[editar] Ejemplos
#include <array> #include <iostream> #include <numeric> // parámetro de plantilla de no tipo simple template<int N> struct S { int a[N]; }; template<const char*> struct S2 {}; // ejemplo de no tipo complicado template < char c, // tipo entero int (&ra)[5], // referencia lvalue a un objeto (de tipo array) int (*pf)(int), // puntero a función int (S<10>::*a)[10] // puntero a objeto miembro (de tipo int[10]) > struct Complicado { // llama a la función seleccionada en tiempo de compilación // y almacena el resultado en el array seleccionado en tiempo de compilación void foo(char base) { ra[4] = pf(c - base); } }; // S2<"falla"> s2; // ERROR: no puede usarse literal de cadena char okay[] = "de acuerdo"; // objeto estático con enlace // S2< &okay[0] > s3; // ERROR: elemento de array no tiene enlace S2<okay> s4; // funciona int a[5]; int f(int n) { return n; } // C++20: NTTP (parámetro de plantilla de no-tipo) puede ser un tipo de clase literal template<std::array arr> constexpr auto suma() { return std::accumulate(arr.cbegin(), arr.cend(), 0); } // C++20: se deducen los argumentos de plantilla de clase en el lugar de la llamada static_assert(suma<std::array<double, 8>{3, 1, 4, 1, 5, 9, 2, 6}>() == 31.0); // C++20: deducción de argumento NTTP y CTAD (deducción de argumento de plantilla de clase) static_assert(suma<std::array{2, 7, 1, 8, 2, 8}>() == 28); int main() { S<10> s; // s.a es un array de 10 enteros s.a[9] = 4; Complicado<'2', a, f, &S<10>::a> c; c.foo('0'); std::cout << s.a[9] << a[4] << '\n'; }
Salida:
42
Esta sección está incompleta Razón: más ejemplos |
[editar] Informes de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
---|---|---|---|
CWG 150 | C++98 | los argumentos de plantilla de plantilla tenían que coincidir exactamente con las listas de parámetros de plantilla de plantilla |
también se permite más especializado (resuelto por P0522R0) |
CWG 184 | C++98 | no se especifica si los parámetros de plantilla de los parámetros de plantilla de plantilla pueden tener argumentos predeterminados |
se añade especificación |
CWG 354 | C++98 | los valores puntero nulo no podían ser argumentos de plantilla de no-tipo | permitido |
CWG 1398 | C++11 | los argumentos de plantilla de no tipo no podían tener el tipostd::nullptr_t
|
permitido |
CWG 1570 | C++98 | los argumentos de plantilla de no-tipo podían designar la dirección de subobjetos |
no permitido |
CWG 1922 | C++98 | no estaba claro si una plantilla de clase cuyo nombre es un nombre de clase inyectado puede usar los argumentos predeterminados en declaraciones anteriores |
permitido |
CWG 2032 | C++14 | para plantillas de variable, no había restricción en los parámetros de plantilla después de un parámetros de plantilla con un argumento predeterminado |
se aplica la misma restricción que en plantillas de clase y de alias |