Namespaces
Variants

Partial template specialization

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

Permite personalizar plantillas de clase y variable (desde C++14) para una categoría determinada de argumentos de plantilla.

Contenidos

Sintaxis

template < lista-de-parámetros > clave-de-clase nombre-de-cabecera-de-clase < lista-de-argumentos > declaración (1)
template < lista-de-parámetros > secuencia-de-especificadores-de-declaración declarador < lista-de-argumentos > inicializador  (opcional) (2) (since C++14)

donde class-head-name identifica el nombre de una plantilla de clase previamente declarada y declarator identifica el nombre de una plantilla de variable previamente declarada (desde C++14) .

La especialización parcial puede declararse en cualquier ámbito donde su plantilla principal pueda definirse (lo cual puede ser diferente del ámbito donde se define la plantilla principal; como con la especialización fuera de clase de una plantilla de miembro ). La especialización parcial debe aparecer después de la declaración de la plantilla no especializada.

Por ejemplo,

template<class T1, class T2, int I>
class A {};             // plantilla principal
template<class T, int I>
class A<T, T*, I> {};   // #1: especialización parcial donde T2 es un puntero a T1
template<class T, class T2, int I>
class A<T*, T2, I> {};  // #2: especialización parcial donde T1 es un puntero
template<class T>
class A<int, T*, 5> {}; // #3: especialización parcial donde
                        //     T1 es int, I es 5, y T2 es un puntero
template<class X, class T, int I>
class A<X, T*, I> {};   // #4: especialización parcial donde T2 es un puntero

Ejemplos de especializaciones parciales en la biblioteca estándar incluyen std::unique_ptr , que tiene una especialización parcial para tipos de array.

La lista de argumentos

Las siguientes restricciones se aplican a la argument-list de una especialización parcial de plantilla:

1) La lista de argumentos no puede ser idéntica a la lista de argumentos no especializada (debe especializar algo):
template<class T1, class T2, int I> class B {};        // primary template
template<class X, class Y, int N> class B<X, Y, N> {}; // error

Además, la especialización debe ser más especializada que la plantilla primaria

template<int N, typename T1, typename... Ts> struct B;
template<typename... Ts> struct B<0, Ts...> {}; // Error: not more specialized
(desde C++11)
2) Los argumentos predeterminados no pueden aparecer en la lista de argumentos
3) Si cualquier argumento es una expansión de paquete, debe ser el último argumento en la lista
4) Las expresiones de argumentos constantes pueden utilizar parámetros de plantilla siempre que el parámetro aparezca al menos una vez fuera de un contexto no deducible (nótese que solo clang y gcc 12 admiten actualmente esta característica):
template<int I, int J> struct A {};
template<int I> struct A<I + 5, I * 2> {}; // error, I is not deducible
template<int I, int J, int K> struct B {};
template<int I> struct B<I, I * 2, 2> {};  // OK: first parameter is deducible
5) El argumento constante de plantilla no puede especializar un parámetro de plantilla cuyo tipo depende de un parámetro de la especialización:
template<class T, T t> struct C {}; // primary template
template<class T> struct C<T, 1>;   // error: type of the argument 1 is T,
                                    // which depends on the parameter T
template<int X, int (*array_ptr)[X]> class B {}; // primary template
int array[5];
template<int X> class B<X, &array> {}; // error: type of the argument &array is
                                       // int(*)[X], which depends on the parameter X

Búsqueda de nombres

Las especializaciones parciales de plantillas no se encuentran mediante búsqueda por nombre. Solo si la plantilla principal se encuentra mediante búsqueda por nombre, se consideran sus especializaciones parciales. En particular, una declaración using que hace visible una plantilla principal, también hace visibles las especializaciones parciales:

namespace N
{
    template<class T1, class T2> class Z {}; // plantilla primaria
}
using N::Z; // hace referencia a la plantilla primaria
namespace N
{
    template<class T> class Z<T, T*> {};     // especialización parcial
}
Z<int, int*> z; // la búsqueda de nombre encuentra N::Z (la plantilla primaria),
                // luego se utiliza la especialización parcial con T = int

Ordenamiento parcial

Cuando una clase o variable (since C++14) template se instancia, y hay especializaciones parciales disponibles, el compilador debe decidir si se va a utilizar la plantilla principal o una de sus especializaciones parciales.

1) Si solo una especialización coincide con los argumentos de la plantilla, se utiliza esa especialización
2) Si más de una especialización coincide, se utilizan las reglas de orden parcial para determinar qué especialización es más especializada. Se utiliza la especialización más especializada, si es única (si no es única, el programa no puede compilarse)
3) Si ninguna especialización coincide, se utiliza la plantilla principal
// given the template A as defined above
A<int, int, 1> a1;   // no specializations match, uses primary template
A<int, int*, 1> a2;  // uses partial specialization #1 (T = int, I = 1)
A<int, char*, 5> a3; // uses partial specialization #3, (T = char)
A<int, char*, 1> a4; // uses partial specialization #4, (X = int, T = char, I = 1)
A<int*, int*, 2> a5; // error: matches #2 (T = int, T2 = int*, I= 2)
                     //        matches #4 (X = int*, T = int, I = 2)
                     // neither one is more specialized than the other

Informalmente, "A está más especializado que B" significa "A acepta un subconjunto de los tipos que B acepta".

Formalmente, para establecer la relación de más-especializada-que entre especializaciones parciales, cada una se convierte primero en una plantilla de función ficticia de la siguiente manera:

  • la primera plantilla de función tiene los mismos parámetros de plantilla que la primera especialización parcial y tiene solo un parámetro de función, cuyo tipo es una especialización de plantilla de clase con todos los argumentos de plantilla de la primera especialización parcial
  • la segunda plantilla de función tiene los mismos parámetros de plantilla que la segunda especialización parcial y tiene solo un parámetro de función cuyo tipo es una especialización de plantilla de clase con todos los argumentos de plantilla de la segunda especialización parcial.

Las plantillas de función se clasifican como si fuera para function template overloading .

template<int I, int J, class T> struct X {}; // plantilla principal
template<int I, int J>          struct X<I, J, int>
{
    static const int s = 1;
}; // especialización parcial #1
// plantilla de función ficticia para #1 es
// template<int I, int J> void f(X<I, J, int>); #A
template<int I>                 struct X<I, I, int>
{
    static const int s = 2;
}; // especialización parcial #2
// plantilla de función ficticia para #2 es 
// template<int I>        void f(X<I, I, int>); #B
int main()
{
    X<2, 2, int> x; // tanto #1 como #2 coinciden
// ordenamiento parcial para plantillas de función:
// #A desde #B: void(X<I, J, int>) desde void(X<U1, U1, int>): deducción OK
// #B desde #A: void(X<I, I, int>) desde void(X<U1, U2, int>): deducción falla
// #B es más especializada
// #2 es la especialización que se instancia
    std::cout << x.s << '\n'; // imprime 2
}

Miembros de especializaciones parciales

La lista de parámetros de plantilla y la lista de argumentos de plantilla de un miembro de una especialización parcial deben coincidir con la lista de parámetros y la lista de argumentos de la especialización parcial.

Al igual que con los miembros de las plantillas primarias, solo necesitan definirse si se utilizan en el programa.

Los miembros de las especializaciones parciales no están relacionados con los miembros de la plantilla principal.

La especialización explícita (completa) de un miembro de una especialización parcial se declara de la misma manera que una especialización explícita de la plantilla principal.

template<class T, int I> // plantilla principal
struct A
{
    void f(); // declaración de miembro
};
template<class T, int I>
void A<T, I>::f() {}     // definición de miembro de plantilla principal
// especialización parcial
template<class T>
struct A<T, 2>
{
    void f();
    void g();
    void h();
};
// miembro de especialización parcial
template<class T>
void A<T, 2>::g() {}
// especialización explícita (completa)
// de un miembro de especialización parcial
template<>
void A<char, 2>::h() {}
int main()
{
    A<char, 0> a0;
    A<char, 2> a2;
    a0.f(); // OK, usa la definición de miembro de la plantilla principal
    a2.g(); // OK, usa la definición de miembro de la especialización parcial
    a2.h(); // OK, usa la definición completamente especializada de
            // el miembro de una especialización parcial
    a2.f(); // error: no hay definición de f() en la
            // especialización parcial A<T,2> (no se usa la plantilla principal)
}

Si una plantilla primaria es miembro de otra plantilla de clase, sus especializaciones parciales son miembros de la plantilla de clase envolvente. Si la plantilla envolvente se instancia, la declaración de cada especialización parcial miembro también se instancia (de la misma manera que las declaraciones, pero no las definiciones, de todos los demás miembros de una plantilla se instancian).

Si la plantilla de miembro principal está explícitamente (completamente) especializada para una especialización (implícita) dada de la plantilla de clase envolvente, las especializaciones parciales de la plantilla de miembro se ignoran para esta especialización de la plantilla de clase envolvente.

Si una especialización parcial de la plantilla de miembro está explícitamente especializada para una especialización (implícita) dada de la plantilla de clase envolvente, la plantilla de miembro primaria y sus otras especializaciones parciales aún se consideran para esta especialización de la plantilla de clase envolvente.

template<class T> struct A // plantilla de clase envolvente
{
    template<class T2>
    struct B {};      // plantilla de miembro primaria
    template<class T2>
    struct B<T2*> {}; // especialización parcial de plantilla de miembro
};
template<>
template<class T2>
struct A<short>::B {}; // especialización completa de plantilla de miembro primaria
                       // (ignorará la parcial)
A<char>::B<int*> abcip;  // usa especialización parcial T2=int
A<short>::B<int*> absip; // usa especialización completa de la primaria (ignora parcial)
A<char>::B<int> abci;    // usa primaria

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 727 C++98 especializaciones parciales y completas no permitidas en
ámbito de clase
permitidas en cualquier ámbito
CWG 1315 C++98 el parámetro de plantilla no podía usarse en argumentos
constantes de plantilla excepto en expresiones-id
expresiones válidas mientras sean deducibles
CWG 1495 C++11 la especificación no era clara al involucrar paquetes de parámetros la especialización debe ser más especializada
CWG 1711 C++14 falta de especificación para especializaciones parciales de plantillas de variables añadir soporte para plantillas de variables
CWG 1819 C++98 ámbitos aceptables para la definición de especialización parcial permitir que la especialización parcial pueda declararse
en el mismo ámbito que las plantillas primarias
CWG 2330 C++14 faltaban referencias a plantillas de variables añadir soporte para plantillas de variables

Véase también