Explicit (full) template specialization
Permet de personnaliser le code du modèle pour un ensemble donné d'arguments de modèle.
Table des matières |
Syntaxe
template <>
déclaration
|
|||||||||
N'importe lequel des éléments suivants peut être entièrement spécialisé :
- modèle de fonction
- modèle de classe
- modèle de variable (depuis C++14)
- fonction membre d'un modèle de classe
- membre de données statique d'un modèle de classe
- classe membre d'un modèle de classe
- membre énumération d'un modèle de classe
- modèle de classe membre d'une classe ou d'un modèle de classe
- modèle de fonction membre d'une classe ou d'un modèle de classe
- modèle de variable membre d'une classe ou d'un modèle de classe (depuis C++14)
Par exemple,
#include <type_traits> template<typename T> // primary template struct is_void : std::false_type {}; template<> // explicit specialization for T = void struct is_void<void> : std::true_type {}; int main() { static_assert(is_void<char>::value == false, "for any type T other than void, the class is derived from false_type"); static_assert(is_void<void>::value == true, "but when T is void, the class is derived from true_type"); }
En détail
La spécialisation explicite peut être déclarée dans toute portée où son modèle primaire peut être défini (ce qui peut être différent de la portée où le modèle primaire est défini ; comme avec la spécialisation hors-classe d'un modèle de membre ). La spécialisation explicite doit apparaître après la déclaration du modèle non spécialisé.
namespace N { template<class T> // modèle primaire class X { /*...*/ }; template<> // spécialisation dans le même espace de noms class X<int> { /*...*/ }; template<class T> // modèle primaire class Y { /*...*/ }; template<> // déclaration anticipée de la spécialisation pour double class Y<double>; } template<> // OK : spécialisation dans le même espace de noms class N::Y<double> { /*...*/ };
La spécialisation doit être déclarée avant la première utilisation qui entraînerait une instanciation implicite, dans chaque unité de traduction où une telle utilisation se produit :
class String {}; template<class T> class Array { /*...*/ }; template<class T> // modèle primaire void sort(Array<T>& v) { /*...*/ } void f(Array<String>& v) { sort(v); // instancie implicitement sort(Array<String>&), } // en utilisant le modèle primaire pour sort() template<> // ERREUR : spécialisation explicite de sort(Array<String>) void sort<String>(Array<String>& v); // après l'instanciation implicite
Une spécialisation de modèle qui a été déclarée mais non définie peut être utilisée comme n'importe quel autre type incomplet (par exemple, les pointeurs et références vers celui-ci peuvent être utilisés) :
template<class T> // modèle primaire class X; template<> // spécialisation (déclarée, non définie) class X<int>; X<int>* p; // OK : pointeur vers type incomplet X<int> x; // erreur : objet de type incomplet
Qu'une spécialisation explicite d'un modèle de fonction
ou de variable
(since C++14)
soit
inline
/
constexpr
(since C++11)
/
constinit
/
consteval
(since C++20)
est déterminé par la spécialisation explicite elle-même, indépendamment du fait que le modèle principal soit déclaré avec ce spécificateur.
De même,
les attributs
apparaissant dans la déclaration d'un modèle n'ont aucun effet sur une spécialisation explicite de ce modèle :
(since C++11)
template<class T> void f(T) { /* ... */ } template<> inline void f<>(int) { /* ... */ } // OK, inline template<class T> inline T g(T) { /* ... */ } template<> int g<>(int) { /* ... */ } // OK, pas inline template<typename> [[noreturn]] void h([[maybe_unused]] int i); template<> void h<int>(int i) { // [[noreturn]] n'a aucun effet, mais [[maybe_unused]] en a }
Spécialisations explicites des modèles de fonction
Lors de la spécialisation d'un modèle de fonction, ses arguments template peuvent être omis si la déduction d'arguments template peut les fournir à partir des arguments de la fonction :
template<class T> class Array { /*...*/ }; template<class T> // modèle primaire void sort(Array<T>& v); template<> // spécialisation pour T = int void sort(Array<int>&); // pas besoin d'écrire // template<> void sort<int>(Array<int>&);
Une fonction portant le même nom et la même liste d'arguments qu'une spécialisation n'est pas une spécialisation (voir la surcharge de modèles dans function template ).
Arguments par défaut des fonctions ne peuvent pas être spécifiés dans les spécialisations explicites des modèles de fonctions, des modèles de fonctions membres et des fonctions membres des modèles de classe lorsque la classe est instanciée implicitement.
Une spécialisation explicite ne peut pas être une déclaration friend .
|
Cette section est incomplète
Motif : revoir l'exigence de spécification d'exception à travers les différentes versions de C++ |
Membres des spécialisations
Lors de la définition d'un membre d'un modèle de classe explicitement spécialisé en dehors du corps de la classe, la syntaxe template <> n'est pas utilisée, sauf s'il s'agit d'un membre d'un modèle de classe membre explicitement spécialisé, qui est spécialisé en tant que modèle de classe, car autrement, la syntaxe exigerait qu'une telle définition commence par template < parameters > requis par le modèle imbriqué
template<typename T> struct A { struct B {}; // classe membre template<class U> // modèle de classe membre struct C {}; }; template<> // spécialisation struct A<int> { void f(int); // fonction membre d'une spécialisation }; // template<> non utilisé pour un membre d'une spécialisation void A<int>::f(int) { /* ... */ } template<> // spécialisation d'une classe membre struct A<char>::B { void f(); }; // template<> non utilisé non plus pour un membre d'une classe membre spécialisée void A<char>::B::f() { /* ... */ } template<> // spécialisation d'un modèle de classe membre template<class U> struct A<char>::C { void f(); }; // template<> est utilisé lors de la définition d'un membre d'un // modèle de classe membre explicitement spécialisé en tant que modèle de classe template<> template<class U> void A<char>::C<U>::f() { /* ... */ }
Une spécialisation explicite d'un membre de données statique d'un modèle est une définition si la déclaration inclut un initialiseur ; sinon, c'est une déclaration. Ces définitions doivent utiliser des accolades pour l'initialisation par défaut :
template<> X Q<int>::x; // déclaration d'un membre statique template<> X Q<int>::x (); // erreur : déclaration de fonction template<> X Q<int>::x {}; // définition d'un membre statique initialisé par défaut
Un membre ou un patron de membre d'un patron de classe peut être explicitement spécialisé pour une instanciation implicite donnée du patron de classe, même si le membre ou le patron de membre est défini dans la définition du patron de classe.
template<typename T> struct A { void f(T); // membre, déclaré dans le template primaire void h(T) {} // membre, défini dans le template primaire template<class X1> // template de membre void g1(T, X1); template<class X2> // template de membre void g2(T, X2); }; // spécialisation d'un membre template<> void A<int>::f(int); // spécialisation de membre OK même si définie dans la classe template<> void A<int>::h(int) {} // définition de template de membre hors classe template<class T> template<class X1> void A<T>::g1(T, X1) {} // spécialisation de template de membre template<> template<class X1> void A<int>::g1(int, X1); // spécialisation de template de membre template<> template<> void A<int>::g2<char>(int, char); // pour X2 = char // même chose, en utilisant la déduction d'arguments de template (X1 = char) template<> template<> void A<int>::g1(int, char);
Un membre ou un patron de membre peut être imbriqué dans plusieurs patrons de classe englobants. Dans une spécialisation explicite pour un tel membre, il y a un template <> pour chaque patron de classe englobant qui est explicitement spécialisé.
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>();
Dans une telle déclaration imbriquée, certains niveaux peuvent rester non spécialisés (sauf qu'on ne peut pas spécialiser un modèle de membre de classe dans la portée de l'espace de noms si sa classe englobante n'est pas spécialisée). Pour chacun de ces niveaux, la déclaration nécessite template < arguments > , car ces spécialisations sont elles-mêmes des modèles :
template<class T1> class A { template<class T2> class B { template<class T3> // template membre void mf1(T3); void mf2(); // membre non-template }; }; // spécialisation template<> // pour le A spécialisé template<class X> // pour le B non spécialisé class A<int>::B { template<class T> void mf1(T); }; // spécialisation template<> // pour le A spécialisé template<> // pour le B spécialisé template<class T> // pour le mf1 non spécialisé void A<int>::B<double>::mf1(T t) {} // ERREUR : B<double> est spécialisé et est un template membre, donc son A englobant // doit également être spécialisé template<class Y> template<> void A<Y>::B<double>::mf2() {}
Rapports de défauts
Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.
| DR | Appliqué à | Comportement publié | Comportement corrigé |
|---|---|---|---|
| CWG 531 | C++98 |
la syntaxe de définition des membres des spécialisations
explicites dans la portée de l'espace de noms n'était pas spécifiée |
spécifiée |
| CWG 727 | C++98 |
les spécialisations partielles et complètes non autorisées dans
la portée de classe |
autorisées dans toute portée |
| CWG 730 | C++98 |
les modèles de membres des classes non modèles
ne pouvaient pas être entièrement spécialisés |
autorisé |
| CWG 2478 | C++20 |
il n'était pas clair si le
constinit
et le
consteval
du
modèle principal sont reportés dans ses spécialisations explicites |
non reportés |
| CWG 2604 | C++11 |
il n'était pas clair si les attributs du modèle
principal sont reportés dans ses spécialisations explicites |
non reportés |