Especialización de plantilla explícita (Completa)
Permite personalizar el código de plantilla para un conjunto dado de argumentos de plantilla.
Contenido |
[editar]Sintaxis
template <> declaración | |||||||||
Cualquiera de los siguientes puede ser completamente especializado:
- plantilla de función
- plantilla de clase
- plantilla de variable(desde C++14)
- función miembro de una plantilla de clase
- miembro estático de datos de una plantilla de clase
- clase miembro de una plantilla de clase
- miembro enumeración de una plantilla de clase
- miembro plantilla de clase de una clase o plantilla de clase
- miembro plantilla de función de una clase o plantilla de clase
- miembro plantilla de variable de una clase o plantilla de clase(desde C++14)
Por ejemplo,
#include <type_traits> template<typename T>// plantilla principalstruct es_void :std::false_type{};template<>// especialización explícita para T = voidstruct es_void<void>:std::true_type{}; int main(){ static_assert(es_void<char>::value==false, "para cualquier tipo T que no sea void, la clase se deriva de false_type"); static_assert(es_void<void>::value==true, "pero cuando T es void, la clase se deriva de true_type");}
[editar]En detalle
La especialización explícita se puede declarar en cualquier ámbito donde se pueda definir su plantilla principal (que puede ser diferente del ámbito en el que se define la plantilla principal, como con la especialización fuera de clase de una plantilla miembro). La especialización de plantilla tiene que aparecer después de la declaración de plantilla no especializada.
namespace N {template<class T>// plantilla principalclass X {/*...*/};template<>// especialización en el mismo espacio de nombresclass X<int>{/*...*/}; template<class T>// plantilla principalclass Y {/*...*/};template<>// declaración adelantada de especialización para doubleclass Y<double>;} template<>// correcto: especialización en el mismo espacio de nombresclass N::Y<double>{/*...*/};
La especialización debe declarase antes del primer uso que causaría la instanciación implícita, en cada unidad de traducción donde ocurra tal uso:
class String {}; template<class T>class Array {/*...*/}; template<class T>// plantilla principalvoid sort(Array<T>& v){/*...*/} void f(Array<String>& v){ sort(v);// instancia implícita de sort(Array<String>&), }// usando la plantilla principal para sort() template<>// ERROR: especialización explícita para sort(Array<String>)void sort<String>(Array<String>& v);// después de la instanciación implícita
Una especialización de plantilla que se declaró pero no se definió se puede usar como cualquier otro tipo incompleto (por ejemplo, se podrían usar punteros y referencias a él):
template<class T>// plantilla principalclass X;template<>// especialización (declarado, no definido)class X<int>; X<int>* p;// correcto: puntero a tipo incompleto X<int> x;// error: objecto de tipo incompleto
Si una especialización explícita de una plantilla de función o variable(desde C++14) es inline
/constexpr
(desde C++11)/constinit
/consteval
(desde C++20) está determinada por la propia especialización explícita, independientemente de si la plantilla principal se declara con ese especificador. De manera similar, los atributos que aparecen en la declaración de una plantilla no tienen efecto en una especialización explícita de esa plantilla:(desde C++11)
template<class T>void f(T){/* ... */}template<>inlinevoid f<>(int){/* ... */}// correcto, inline template<class T>inline T g(T){/* ... */}template<>int g<>(int){/* ... */}// correcto, no inline template<typename>[[noreturn]]void h([[maybe_unused]]int i);template<>void h<int>(int i){// [[noreturn]] no tiene efecto, pero [[maybe_unused]] si}
[editar]Especialización explícita de plantillas de función
Al especializar una plantilla de función, sus argumentos de plantilla se pueden omitir si la deducción de argumentos de plantilla puede proporcionarlos a partir de los argumentos de función:
template<class T>class Array {/*...*/}; template<class T>// plantilla principalvoid sort(Array<T>& v);template<>// especialización para T = intvoid sort(Array<int>&); // no es necesario escribir// template<> void sort<int>(Array<int>&);
Una función con el mismo nombre y la misma lista de argumentos que una especialización no es una especialización (ver sobrecarga de plantillas en plantilla de función).
Los argumentos de función predeterminados no se pueden especificar en especializaciones explícitas de plantillas de función, plantillas de función miembro y funciones miembro de plantillas de clase cuando la clase se crea de forma implícita.
Una especialización explícita no puede ser una declaración friend.
Esta sección está incompleta Razón: revisar el requisito de especificación de excepción en diferentes versiones de C++ |
[editar]Miembros de especializaciones
Cuando se define un miembro de una plantilla de clase explícitamente especializada fuera del cuerpo de la clase, la sintaxis template<> no se usa, excepto si es miembro de una plantilla de clase de miembro explícitamente especializada, que está especializada como plantilla de clase, porque de lo contrario, la sintaxis requeriría esta definición para comenzar con template<parámetros> requerido por la plantilla anidada.
template<typename T>struct A {struct B {};// clase miembro template<class U>// miembro plantilla de clasestruct C {};}; template<>// especializaciónstruct A<int>{void f(int);// función miembro de una especialización};// no se usa template<> para un miembro de una especializaciónvoid A<int>::f(int){/* ... */} template<>// especialización de una clase miembrostruct A<char>::B{void f();};// tampoco se usa template<> para un miembro de una clase miembro especializadavoid A<char>::B::f(){/* ... */} template<>// especialización de una plantilla de clase miembrotemplate<class U>struct A<char>::C{void f();}; // se usa template<> cuando se define un miembro de una plantilla de clase// miembro especificado explícitamente como una plantilla de clasetemplate<>template<class U>void A<char>::C<U>::f(){/* ... */}
Una especialización explícita de un miembro de datos estático de una plantilla es una definición si la declaración incluye un inicializador; de lo contrario, es una declaración. Estas definiciones deben usar llaves para la inicialización predeterminada:
template<> X Q<int>::x;// declaración de un miembro estáticotemplate<> X Q<int>::x();// error: declaración de funcióntemplate<> X Q<int>::x{};// definición de un miembro estático inicializado por defecto
Un miembro o plantilla de miembro de una plantilla de clase puede especializarse explícitamente para una instanciación implícita dada de la plantilla de clase, incluso si el miembro o plantilla de miembro está definido en la definición de plantilla de clase.
template<typename T>struct A {void f(T);// miembro, declarado en la plantilla primaria void h(T){}// miembro, definido en la plantilla primaria template<class X1>// plantilla de miembrovoid g1(T, X1); template<class X2>// plantilla de miembrovoid g2(T, X2);}; // especialización de un miembrotemplate<>void A<int>::f(int); // especialización de miembro, correcto incluso si está definido en la clasetemplate<>void A<int>::h(int){} // definición de plantilla de miembro fuera de la clasetemplate<class T>template<class X1>void A<T>::g1(T, X1){} // especialización de plantilla de miembrotemplate<>template<class X1>void A<int>::g1(int, X1); // especialización de plantilla de miembrotemplate<>template<>void A<int>::g2<char>(int, char);// para X2 = char // igual, usando la deducción de argumentos de plantila (X1 = char)template<>template<>void A<int>::g1(int, char);
Un miembro o plantilla de miembro se puede anidar dentro de muchas plantillas de clase adjuntas. En una especialización explícita para un miembro de este tipo, hay una template<> para cada plantilla de clase adjunta que esté explícitamente especializada.
template<class T1>struct A {template<class T2>struct B {template<class T3>void mf();};}; template<>struct A<int>; template<>template<>struct A<char>::B<double>; template<>template<>template<>void A<char>::B<char>::mf<double>();
En tal declaración anidada, algunos de los niveles pueden permanecer no especializados (excepto que no puede especializar una plantilla de miembro de clase si su clase adjunta no está especializada). Para cada uno de esos niveles, la declaración necesita template<argumentos>, porque tales especializaciones son en sí misma plantillas:
template<class T1>class A {template<class T2>class B {template<class T3>// plantilla de miembrovoid mf1(T3); void mf2();// miembro no plantilla};}; // especializacióntemplate<>// para la especializada Atemplate<class X>// para la no especializada Bclass A<int>::B{template<class T>void mf1(T);}; // especializacióntemplate<>// para la especializada Atemplate<>// para la especializada Btemplate<class T>// para la no especializada mf1void A<int>::B<double>::mf1(T t){} // ERROR: B<double> es especializada y un miembro plantilla, por lo que su A adjunta// también debe estar especializadatemplate<class Y>template<>void A<Y>::B<double>::mf2(){}
[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 531 | C++98 | no se especificó la sintaxis para definir miembros de especializaciones explícitas en el ámbito del espacio de nombre | especificado |
CWG 727 | C++98 | no se permiten especificaciones completas en el ámbito de clase, aunque si las parciales | se permite la especialización completa en cualquier ámbito |
CWG 730 | C++98 | las plantillas de miembros de clases que no son plantillas no se pueden especializar completamente | permitido |
CWG 2478 | C++20 | no estaba claro si constinit y consteval de la plantilla principal se trasladan a sus especializaciones explícitas | no se transfiere |
CWG 2604 | C++11 | no estaba claro si los atributos de la plantilla principal se transfieren a sus especializaciones explícitas | no se transfiere |