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

Cours de C/C++
^
Les templates
Généralités
Déclaration des paramètres template
Fonctions et classes template
Instanciation des template
Spécialisation des template
Mot-clé typename
Fonctions exportées

Livre original de C. Casteyde

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

modifier

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

modifier
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;
}
 Pour quelques compilateurs, la ligne déclarant la liste vide des paramètres template ne doit pas être écrite. On doit donc faire des spécialisations totale sans le mot clé template. Ce comportement n'est pas celui spécifié par la norme, et le code écrit pour ces compilateurs n'est donc pas portable.

Spécialisation partielle

modifier

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

modifier
// 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 :

  • une valeur ne peut pas être exprimée en fonction d'un paramètre template de la spécialisation ;
    template <int I, int J>
    struct B
    {
    };
    
    template <int I>
    struct B<I, I*2>   // Erreur !
    {                   // Spécialisation incorrecte !
    };
    
  • le type d'une des valeurs de la spécialisation ne peut pas dépendre d'un autre paramètre ;
    template <class T, T t>
    struct C
    {
    };
    
    template <class T>
    struct C<T, 1>;    // Erreur !
                       // Spécialisation incorrecte !
    
  • la liste des arguments de la spécialisation ne doit pas être identique à la liste implicite de la déclaration template correspondante.

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

modifier

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

modifier
#include <iostream>

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;
}