Namespaces
Variants

Template arguments

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

Para que una plantilla sea instanciada, cada parámetro de plantilla debe ser reemplazado por un argumento de plantilla correspondiente. Los argumentos son proporcionados explícitamente, deducidos o establecidos por defecto.

Cada parámetro en template-parameter-list (ver sintaxis de identificador de plantilla ) pertenece a una de las siguientes categorías:

  • argumento de plantilla constante
  • argumento de plantilla de tipo
  • argumento de plantilla de plantilla

Contenidos

Argumentos de plantilla constantes

También conocido como non-type template arguments (ver abajo ).

El argumento de plantilla que puede utilizarse con un parámetro de plantilla constante puede ser cualquier expresión manifiestamente constante-evaluada .

(until C++11)

El argumento de plantilla que puede utilizarse con un parámetro de plantilla constante puede ser cualquier cláusula de inicialización . Si la cláusula de inicialización es una expresión, debe ser manifiestamente constante-evaluada .

(since C++11)

Dado el tipo de la declaración del parámetro de plantilla constante como T y el argumento de plantilla proporcionado para el parámetro como E .

La declaración inventada T x = E ; debe satisfacer las restricciones semánticas para la definición de una constexpr variable con duración de almacenamiento estático .

(desde C++20)

Si T contiene un tipo de marcador de posición , o es un marcador de posición para un tipo de clase deducido , el tipo del parámetro de plantilla es el tipo deducido para la variable x en la declaración inventada T x = E ; .

Si un tipo de parámetro deducido no es un tipo estructural , el programa está mal formado.

Para paquetes de parámetros de plantilla constantes cuyo tipo utiliza un tipo de marcador de posición, el tipo se deduce independientemente para cada argumento de plantilla y no necesita coincidir.

(desde C++17)
template<auto n>
struct B { /* ... */ };
B<5> b1;   // OK: el tipo del parámetro de plantilla constante es int
B<'a'> b2; // OK: el tipo del parámetro de plantilla constante es char
B<2.5> b3; // error (hasta C++20): el tipo del parámetro de plantilla constante no puede ser double
// Marcador de posición de tipo de clase deducido en C++20, los argumentos de plantilla de clase se deducen en el
// sitio de llamada
template<std::array arr>
void f();
f<std::array<double, 8>{}>();
template<auto...>
struct C {};
C<'C', 0, 2L, nullptr> x; // OK

El valor de un parámetro de plantilla constante P de tipo (posiblemente deducido) (since C++17) T se determina a partir de su argumento de plantilla A de la siguiente manera:

(hasta C++11)
  • Si A es una expresión:
  • En caso contrario ( A es una lista de inicialización entre llaves), se introduce una variable temporal constexpr T v = A ; . El valor de P es el de v .
(desde C++11)
(hasta C++20)
  • Si T no es un tipo clase y A es una expresión:
  • En caso contrario ( T es un tipo clase o A es una lista de inicialización entre llaves), se introduce una variable temporal constexpr T v = A ; .
  • El tiempo de vida de v termina inmediatamente después de inicializar v y P .
  • Si la inicialización de P cumple alguna de las siguientes condiciones, el programa está mal formado:
  • En caso contrario, el valor de P es el de v .
(desde C++20)
template<int i>
struct C { /* ... */ };
C<{42}> c1; // CORRECTO
template<auto n>
struct B { /* ... */ };
struct J1
{
    J1* self = this;
};
B<J1{}> j1; // error: la inicialización del objeto parámetro de plantilla
            //        no es una expresión constante
struct J2
{
    J2 *self = this;
    constexpr J2() {}
    constexpr J2(const J2&) {}
};
B<J2{}> j2; // error: el objeto parámetro de plantilla no es
            //        equivalente-por-argumento-de-plantilla al temporal introducido

Las siguientes limitaciones aplican al instanciar plantillas que tienen parámetros de plantilla constantes:

  • Para tipos integrales y aritméticos, el argumento de plantilla proporcionado durante la instanciación debe ser una expresión constante convertida del tipo del parámetro de plantilla (por lo que aplican ciertas conversiones implícitas).
  • Para punteros a objetos, los argumentos de plantilla deben designar la dirección de un objeto completo con duración de almacenamiento estático y un enlace (ya sea interno o externo), o una expresión constante que evalúe al valor de puntero nulo apropiado o std::nullptr_t (desde C++11) .
  • Para punteros a funciones, los argumentos válidos son punteros a funciones con enlace (o expresiones constantes que evalúen a valores de puntero nulo).
  • Para parámetros de referencia lvalue, el argumento proporcionado en la instanciación no puede ser un temporal, un lvalue sin nombre o un lvalue con nombre sin enlace (en otras palabras, el argumento debe tener enlace).
  • Para punteros a miembros, el argumento debe ser un puntero a miembro expresado como & Class :: Member o una expresión constante que evalúe a puntero nulo o std::nullptr_t (desde C++11) .

En particular, esto implica que los literales de cadena, las direcciones de elementos de array y las direcciones de miembros no estáticos no pueden usarse como argumentos de plantilla para instanciar plantillas cuyos correspondientes parámetros de plantilla constantes son punteros a objetos.

(hasta C++17)

los parámetros de plantilla constantes de tipo referencia o puntero y los miembros de datos no estáticos de tipo referencia o puntero en un parámetro de plantilla constante de tipo clase y sus subobjetos (desde C++20) no pueden referir a/ser la dirección de

  • un objeto temporal (incluyendo uno creado durante inicialización de referencia );
  • un literal de cadena ;
  • el resultado de typeid ;
  • la variable predefinida __func__ ;
  • o un subobjeto (incluyendo miembro de clase no estático, subobjeto base o elemento de array) de uno de los anteriores (desde C++20) .
(desde C++17)
template<const int* pci>
struct X {};
int ai[10];
X<ai> xi; // CORRECTO: 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;   // CORRECTO: sin conversión
template<int (&pa)[5]>
struct W {};
int b[5];
W<b> w;   // CORRECTO: sin conversión
void f(char);
void f(int);
template<void (*pf)(int)>
struct A {};
A<&f> a;  // CORRECTO: la resolución de sobrecarga selecciona f(int)
template<class T, const char* p>
class X {};
X<int, "Studebaker"> 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;  // OK: dirección de miembro estático
X<&S::s> x6; // OK: dirección de miembro estático
template<const int& CRI>
struct B {};
B<1> b2;     // error: se requeriría un temporal para el argumento de plantilla
int c = 1;
B<c> b1;     // OK

Argumentos de plantilla de tipo

Un argumento de plantilla para un parámetro de plantilla de tipo debe ser un type-id , que puede nombrar un tipo incompleto:

template<typename T>
class X {}; // plantilla de clase
struct A;            // tipo incompleto
typedef struct {} B; // alias de tipo para un tipo sin nombre
int main()
{
    X<A> x1;  // OK: 'A' nombra un tipo
    X<A*> x2; // OK: 'A*' nombra un tipo
    X<B> x3;  // OK: 'B' nombra un tipo
}

Argumentos de plantilla de plantilla

Un argumento de plantilla para un parámetro de plantilla de plantilla debe ser una expresión de identificador que nombre una plantilla de clase o un alias de plantilla.

Cuando el argumento es una plantilla de clase, solo se considera la plantilla principal al hacer coincidir el parámetro. Las especializaciones parciales, si las hay, solo se consideran cuando se instancia una especialización basada en este parámetro de plantilla de plantilla.

template<typename T> // plantilla principal
class A { int x; };
template<typename T> // especialización parcial
class A<T*> { long x; };
// plantilla de clase con un parámetro de plantilla V
template<template<typename> class V>
class C
{
    V<int> y;  // utiliza la plantilla principal
    V<int*> z; // utiliza la especialización parcial
};
C<A> c; // c.y.x tiene tipo int, c.z.x tiene tipo long

Para que un argumento de plantilla de plantilla A coincida con un parámetro de plantilla de plantilla P , P debe ser al menos tan especializado 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 son emparejados por é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 a 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 de A (incluyendo argumentos por defecto):

  • Cada una de las dos plantillas de función tiene los mismos parámetros de plantilla, respectivamente, que P o A .
  • 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 plantilla PP en la lista de parámetros de plantilla de la plantilla de función, se forma un argumento de plantilla correspondiente AA . Si PP declara un paquete de parámetros, entonces AA es la expansión de paquete PP... ; de lo contrario, (since C++11) AA es la expresión de identificación PP .

Si la reescritura produce un tipo inválido, entonces P no es al menos tan especializado como A .

template<typename T>
struct eval;                     // plantilla principal
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;        // OK: coincide con la especialización parcial de eval
eval<B<int, float>> eB; // OK: coincide con la especialización parcial de eval
eval<C<17>> eC;         // error: C no coincide con 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 nombra 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 nombra 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 una constante

Antes de la adopción de P0522R0 , cada uno de los parámetros de plantilla de A debía coincidir exactamente con los parámetros de plantilla correspondientes de P . Esto impedía que muchos argumentos de plantilla razonables fueran aceptados.

Aunque fue señalado muy temprano ( CWG#150 ), para cuando se resolvió, los cambios se aplicaron al documento 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 desactiva en todos los modos de lenguaje anteriores a C++17 por defecto, solo puede habilitarse configurando un flag del compilador en estos modos.
  • Clang lo desactiva en todos los modos de lenguaje por defecto, solo puede habilitarse configurando un flag del compilador.
  • Microsoft Visual Studio lo trata como una característica normal de C++17 y solo lo habilita en modos de lenguaje C++17 y posteriores (es decir, no hay soporte 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; // CORRECTO
X<B> xb; // CORRECTO después de P0522R0
         // Error anteriormente: no coincide exactamente
X<C> xc; // CORRECTO después de P0522R0
         // Error anteriormente: no coincide exactamente
template<template<class...> class Q> class Y { /* ... */ };
Y<A> ya; // CORRECTO
Y<B> yb; // CORRECTO
Y<C> yc; // CORRECTO
template<auto n> class D { /* ... */ };   // nota: C++17
template<template<int> class R> class Z { /* ... */ };
Z<D> zd; // CORRECTO después de P0522R0: el parámetro de plantilla
         // está más especializado que el argumento de plantilla
template<int> struct SI { /* ... */ };
template<template<auto> class> void FA(); // nota: C++17
FA<SI>(); // Error

Equivalencia de argumentos de plantilla

La equivalencia de argumentos de plantilla se utiliza para determinar si dos identificadores de plantilla son iguales.

Dos valores son template-argument-equivalent si son del mismo tipo y se cumple alguna de las siguientes condiciones:

  • Son de tipo entero o enumeración y sus valores son los mismos.
  • Son de tipo puntero y tienen el mismo valor de puntero.
  • Son de tipo puntero-a-miembro y se refieren al mismo miembro de clase o ambos son el valor de puntero a miembro nulo.
  • Son de tipo referencia a lvalue y se refieren al mismo objeto o función.
(desde C++11)
  • Son de tipo punto flotante y sus valores son idénticos.
  • Son de tipo array (en cuyo caso los arrays deben ser objetos miembro de alguna clase/uniones) y sus elementos correspondientes son equivalentes-en-argumento-de-plantilla.
  • Son de tipo unión y o bien ambos no tienen miembro activo o tienen el mismo miembro activo y sus miembros activos son equivalentes-en-argumento-de-plantilla.
  • Son de un tipo de cierre lambda.
  • Son de tipo clase no-uniones y sus subobjetos directos correspondientes y miembros de referencia son equivalentes-en-argumento-de-plantilla.
(desde C++20)

Resolución de ambigüedad

Si un argumento de plantilla puede ser interpretado tanto como un type-id como una expresión, siempre se interpreta como un type-id, incluso si el parámetro de plantilla correspondiente es constante:

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
}

Notas

Antes de C++26, los argumentos de plantilla constantes se denominaban argumentos de plantilla no tipo en la terminología estándar. La terminología fue cambiada por P2841R6 / PR #7587 .

Macro de prueba de características Valor Std Característica
__cpp_template_template_args 201611L (C++17)
(DR)
Coincidencia de argumentos de plantilla de plantilla
__cpp_nontype_template_args 201411L (C++17) Permitir evaluación constante para todos argumentos de plantilla constantes
201911L (C++20) Tipos de clase y tipos de punto flotante en parámetros de plantilla constantes

Ejemplo

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares publicados anteriormente de C++.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 150
( P0522R0 )
C++98 los argumentos template-template debían coincidir exactamente con las listas
de parámetros de los parámetros template-template
también se permite
el más especializado
CWG 354 C++98 los valores de puntero nulo no podían ser argumentos de plantilla constantes permitido
CWG 1398 C++11 los argumentos de plantilla constantes no podían tener tipo std::nullptr_t permitido
CWG 1570 C++98 los argumentos de plantilla constantes podían designar direcciones de subobjetos no permitido
P2308R1 C++11
C++20
1. la inicialización de lista no estaba permitida para
argumentos de plantilla constantes (C++11)
2. no estaba claro cómo se inicializan los parámetros
de plantilla constantes de tipos de clase (C++20)
1. permitido
2. aclarado