Programmation C-C++/Les templates/Spécialisation des template

Jusqu'à présent, nous avons défini les classes et les fonctions templates d'une manière unique, pour tous les types et toutes les valeurs des paramètres template. Cependant, il peut être intéressant de définir une version particulière d'une classe ou d'une fonction pour un jeu particulier de paramètres template.
Par exemple, la pile de l'Exemple 12-5 peut être implémentée beaucoup plus efficacement si elle stocke des pointeurs plutôt que des objets, sauf si les objets sont petits (ou appartiennent à un des types prédéfinis du langage). Il peut être intéressant de manipuler les pointeurs de manière transparente au niveau de la pile, pour que la méthode pop renvoie toujours un objet, que la pile stocke des pointeurs ou des objets. Afin de réaliser cela, il faut donner une deuxième version de la pile pour les pointeurs.
Le C++ permet tout cela : lorsqu'une fonction ou une classe template a été définie, il est possible de la spécialiser pour un certain jeu de paramètres template. Il existe deux types de spécialisation : les spécialisations totales, qui sont les spécialisations pour lesquelles il n'y a plus aucun paramètre template (ils ont tous une valeur bien déterminée), et les spécialisations partielles, pour lesquelles seuls quelques paramètres template ont une valeur fixée.
Spécialisation totale.
Les spécialisations totales nécessitent de fournir les valeurs des paramètres template, séparées par des virgules et entre les signes d'infériorité et de supériorité, après le nom de la fonction ou de la classe template. Il faut faire précéder la définition de cette fonction ou de cette classe par la ligne suivante :
template <>
qui permet de signaler que la liste des paramètres template pour cette spécialisation est vide (et donc que la spécialisation est totale).
Par exemple, si la fonction Min définie dans l'Exemple 12-4 doit être utilisée sur une structure Structure et se baser sur un des champs de cette structure pour effectuer les comparaisons, elle pourra être spécialisée de la manière suivante :
Exemple 12-12. Spécialisation totale.
struct Structure
 int Clef; // Clef permettant de retrouver des données.
 void *pData; // Pointeur sur les données.
template <>
Structure Min<Structure>(Structure s1, Structure s2)
 if (s1.Clef>s2.Clef)
 return s1;
 else
 return s2;
Spécialisation partielle.
Les spécialisations partielles permettent de définir l'implémentation d'une fonction ou d'une classe template pour certaines valeurs de leurs paramètres template et de garder d'autres paramètres indéfinis. Il est même possible de changer la nature d'un paramètre template (c'est-à-dire préciser s'il s'agit d'un pointeur ou non) et de forcer le compilateur à prendre une implémentation plutôt qu'une autre selon que la valeur utilisée pour ce paramètre est elle-même un pointeur ou non.
Comme pour les spécialisations totales, il est nécessaire de déclarer la liste des paramètres template utilisés par la spécialisation. Cependant, à la différence des spécialisations totales, cette liste ne peut plus être vide.
Comme pour les spécialisations totales, la définition de la classe ou de la fonction template doit utiliser les signes d'infériorité et de supériorité pour donner la liste des valeurs des paramètres template pour la spécialisation.
Exemple 12-13. Spécialisation partielle.
// Définition d'une classe template :
template <class T1, class T2, int I>
class A
};
// Spécialisation n°1 de la classe :
template <class T, int I>
class A<T, T*, I>
};
// Spécialisation n°2 de la classe :
template <class T1, class T2, int I>
class A<T1*, T2, I>
};
// Spécialisation n°3 de la classe :
template <class T>
class A<int, T*, 5>
};
// Spécialisation n°4 de la classe :
template <class T1, class T2, int I>
class A<T1, T2*, I>
};
On notera que le nombre des paramètres template déclarés à la suite du mot clé template peut varier, mais que le nombre de valeurs fournies pour la spécialisation est toujours constant (dans l'exemple précédent, il y en a trois).
Les valeurs utilisées dans les identificateurs template des spécialisations doivent respecter les règles suivantes :
Enfin, la liste des paramètres template de la déclaration d'une spécialisation ne doit pas contenir des valeurs par défaut. On ne pourrait d'ailleurs les utiliser en aucune manière.
Spécialisation d'une méthode d'une classe template.
La spécialisation partielle d'une classe peut parfois être assez lourde à employer, en particulier si la structure de données qu'elle contient ne change pas entre les versions spécialisées. Dans ce cas, il peut être plus simple de ne spécialiser que certaines méthodes de la classe et non la classe complète. Cela permet de conserver la définition des méthodes qui n'ont pas lieu d'être modifiées pour les différents types, et d'éviter d'avoir à redéfinir les données membres de la classe à l'identique.
La syntaxe permettant de spécialiser une méthode d'une classe template est très simple. Il suffit en effet de considérer la méthode comme une fonction template normale, et de la spécialiser en précisant les paramètres template à utiliser pour cette spécialisation.
Exemple 12-14. Spécialisation de fonction membre de classe template.
using namespace std;
template <class T>
class Item
 T item;
public:
 Item(T);
 void set(T);
 T get(void) const;
 void print(void) const;
template <class T>
Item<T>::Item(T i) // Constructeur
 item = i;
// Accesseurs :
template <class T>
void Item<T>::set(T i)
 item = i;
template <class T>
T Item<T>::get(void) const
 return item;
// Fonction d'affichage générique :
template <class T>
void Item<T>::print(void) const
 cout « item « endl;
// Fonction d'affichage spécialisée explicitement pour le type int *
// et la méthode print :
template <>
void Item<int *>::print(void) const
 cout « *item « endl;

Programmation C-C++/Les templates/Mot-clé typename

Nous avons déjà vu que le mot clé typename pouvait être utilisé pour introduire les types génériques dans les déclarations template. Cependant, il peut être utilisé dans un autre contexte pour introduire les identificateurs de types inconnus dans les template. En effet, un type générique peut très bien être une classe définie par l'utilisateur, à l'intérieur de laquelle des types sont définis. Afin de pouvoir utiliser ces types dans les définitions des template, il est nécessaire d'utiliser le mot clé typename pour les introduire, car a priori le compilateur ne sait pas que le type générique contient la définition d'un autre type. Ce mot clé doit être placé avant le nom complet du type :
typename identificateur
Le mot clé codice_1 est donc utilisé pour signaler au compilateur que l'identificateur identificateur est un type.
Exemple 12-15. Mot-clé typename.
class A
public:
 typedef int Y; // Y est un type défini dans la classe A.
template <class T>
class X
 typename T::Y i; // La classe template X suppose que le
 // type générique T définisse un type Y.
X<A> x; // A peut servir à instancier une classe
 // à partir de la classe template X.

Programmation C-C++/Les templates/Fonctions exportées