12.3. Fonctions et classes template

Après la déclaration d'un ou de plusieurs paramètres template suit en général la déclaration ou la définition d'une fonction ou d'une classe template. Dans cette définition, les types génériques peuvent être utilisés exactement comme s'il s'agissait de types normaux. Les constantes template peuvent être utilisées dans la fonction ou la classe template comme des constantes locales.

12.3.1. Fonctions template

La déclaration et la définition des fonctions template se fait exactement comme si la fonction était une fonction normale, à ceci près qu'elle doit être précédée de la déclaration des paramètres template. La syntaxe d'une déclaration de fonction template est donc la suivante :

template <paramètres_template>
type fonction(paramètres_fonction);
paramètre_template est la liste des paramètres template et paramètres_fonction est la liste des paramètres de la fonction fonction. type est le type de la valeur de retour de la fonction, ce peut être un des types génériques de la liste des paramètres template.

Tous les paramètres template qui sont des types doivent être utilisés dans la liste des paramètres de la fonction, à moins qu'une instanciation explicite de la fonction ne soit utilisée. Cela permet au compilateur de réaliser l'identification des types génériques avec les types à utiliser lors de l'instanciation de la fonction. Voir la Section 12.4 pour plus de détails à ce sujet.

La définition d'une fonction template se fait comme une déclaration avec le corps de la fonction. Il est alors possible d'y utiliser les paramètres template comme s'ils étaient des types normaux : des variables peuvent être déclarées avec un type générique, et les constantes template peuvent être utilisées comme des variables définies localement avec la classe de stockage const. Les fonctions template s'écrivent donc exactement comme des fonctions classiques.

Exemple 12-4. Définition de fonction template

template <class T>
T Min(T x, T y)
{
    return x<y ? x : y;
}

La fonction Min ainsi définie fonctionnera parfaitement pour toute classe pour laquelle l'opérateur < est défini. Le compilateur déterminera automatiquement quel est l'opérateur à employer pour chaque fonction Min qu'il rencontrera.

Les fonctions template peuvent être surchargées, aussi bien par des fonctions classiques que par d'autres fonctions template. Lorsqu'il y a ambiguïté entre une fonction template et une fonction normale qui la surcharge, toutes les références sur le nom commun à ces fonctions se rapporteront à la fonction classique.

Une fonction template peut être déclarée amie d'une classe, template ou non, pourvu que cette classe ne soit pas locale. Toutes les instances générées à partir d'une fonction amie template sont amies de la classe donnant l'amitié, et ont donc libre accès sur toutes les données de cette classe.

12.3.2. Les classes template

La déclaration et la définition d'une classe template se font comme celles d'une fonction template : elles doivent être précédées de la déclaration template des types génériques. La déclaration suit donc la syntaxe suivante :

template <paramètres_template>
class|struct|union nom;
paramètres_template est la liste des paramètres template utilisés par la classe template nom.

La seule particularité dans la définition des classes template est que si les méthodes de la classe ne sont pas définies dans la déclaration de la classe, elles devront elles aussi être déclarées template :

template <paramètres_template>
type classe<paramètres>::nom(paramètres_méthode)
{
    ...
}
paramètre_template représente la liste des paramètres template de la classe template classe, nom représente le nom de la méthode à définir, et paramètres_méthode ses paramètres.

Il est absolument nécessaire dans ce cas de spécifier tous les paramètres template de la liste paramètres_template dans paramètres, séparés par des virgules, afin de caractériser le fait que c'est la classe classe qui est template et qu'il ne s'agit pas d'une méthode template d'une classe normale. D'une manière générale, il faudra toujours spécifier les types génériques de la classe entre les signes d'infériorité et de supériorité, juste après son nom, à chaque fois qu'on voudra la référencer. Cette règle est cependant facultative lorsque la classe est référencée à l'intérieur d'une fonction membre.

Contrairement aux fonctions template non membres, les méthodes des classes template peuvent utiliser des types génériques de leur classe sans pour autant qu'ils soient utilisés dans la liste de leurs paramètres. En effet, le compilateur détermine quels sont les types à identifier aux types génériques lors de l'instanciation de la classe template, et n'a donc pas besoin d'effectuer cette identification avec les types des paramètres utilisés. Voir la Section 12.3.3 pour plus de détails à ce sujet.

Exemple 12-5. Définition d'une pile template

template <class T>
class Stack
{
    typedef struct stackitem
    {
        T Item;                 // On utilise le type T comme
        struct stackitem *Next; // si c'était un type normal.
    } StackItem;

    StackItem *Tete;

public:         // Les fonctions de la pile :
    Stack(void);
    Stack(const Stack<T> &);
                 // La classe est référencée en indiquant
                 // son type entre < et > ("Stack<T>").
                 // Ici, ce n'est pas une nécessité
                 // cependant.
    ~Stack(void);
    Stack<T> &operator=(const Stack<T> &);
    void push(T);
    T pop(void);
    bool is_empty(void) const;
    void flush(void);
};

// Pour les fonctions membres définies en dehors de la déclaration
// de la classe, il faut une déclaration de type générique :

template <class T>
Stack<T>::Stack(void) // La classe est référencée en indiquant
                      // son type entre < et > ("Stack<T>").
                      // C'est impératif en dehors de la
                      // déclaration de la classe.
{
    Tete = NULL;
    return;
}

template <class T>
Stack<T>::Stack(const Stack<T> &Init)
{
    Tete = NULL;
    StackItem *tmp1 = Init.Tete, *tmp2 = NULL;
    while (tmp1!=NULL)
    {
        if (tmp2==NULL)
        {
            Tete= new StackItem;
            tmp2 = Tete;
        }
        else
        {
            tmp2->Next = new StackItem;
            tmp2 = tmp2->Next;
        }
        tmp2->Item = tmp1->Item;
        tmp1 = tmp1->Next;
    }
    if (tmp2!=NULL) tmp2->Next = NULL;
    return;
}

template <class T>
Stack<T>::~Stack(void)
{
    flush();
    return;
}

template <class T>
Stack<T> &Stack<T>::operator=(const Stack<T> &Init)
{
    flush();
    StackItem *tmp1 = Init.Tete, *tmp2 = NULL;

    while (tmp1!=NULL)
    {
        if (tmp2==NULL)
        {
            Tete = new StackItem;
            tmp2 = Tete;
        }
        else
        {
            tmp2->Next = new StackItem;
            tmp2 = tmp2->Next;
        }
        tmp2->Item = tmp1->Item;
        tmp1 = tmp1->Next;
    }
    if (tmp2!=NULL) tmp2->Next = NULL;
    return *this;
}

template <class T>
void Stack<T>::push(T Item)
{
    StackItem *tmp = new StackItem;
    tmp->Item = Item;
    tmp->Next = Tete;
    Tete = tmp;
    return;
}

template <class T>
T Stack<T>::pop(void)
{
    T tmp;
    StackItem *ptmp = Tete;

    if (Tete!=NULL)
    {
        tmp = Tete->Item;
        Tete = Tete->Next;
        delete ptmp;
    }
    return tmp;
}

template <class T>
bool Stack<T>::is_empty(void) const
{
    return (Tete==NULL);
}

template <class T>
void Stack<T>::flush(void)
{
    while (Tete!=NULL) pop();
    return;
}

Les classes template peuvent parfaitement avoir des fonctions amies, que ces fonctions soient elles-mêmes template ou non.

12.3.3. Fonctions membres template

Les destructeurs mis à part, les méthodes d'une classe peuvent être template, que la classe elle-même soit template ou non, pourvu que la classe ne soit pas une classe locale.

Les fonctions membres template peuvent appartenir à une classe template ou à une classe normale.

Lorsque la classe à laquelle elles appartiennent n'est pas template, leur syntaxe est exactement la même que pour les fonctions template non membre.

Exemple 12-6. Fonction membre template

class A
{
    int i;   // Valeur de la classe.
public:
    template <class T>
    void add(T valeur);
};

template <class T>
void A::add(T valeur)
{
    i=i+((int) valeur);   // Ajoute valeur à A::i.
    return ;
}

Si, en revanche, la classe dont la fonction membre fait partie est elle aussi template, il faut spécifier deux fois la syntaxe template : une fois pour la classe, et une fois pour la fonction. Si la fonction membre template est définie à l'intérieur de la classe, il n'est pas nécessaire de donner les paramètres template de la classe, et la définition de la fonction membre template se fait donc exactement comme celle d'une fonction template classique.

Exemple 12-7. Fonction membre template d'une classe template

template<class T>
class Chaine
{
public:
    // Fonction membre template définie
    // à l'extérieur de la classe template :

    template<class T2> int compare(const T2 &);

    // Fonction membre template définie
    // à l'intérieur de la classe template :

    template<class T2>
    Chaine(const Chaine<T2> &s)
    {
        // ...
    }
};

// À l'extérieur de la classe template, on doit donner
// les déclarations template pour la classe
// et pour la fonction membre template :

template<class T> template<class T2>
int Chaine<T>::compare(const T2 &s)
{
    // ...
}

Les fonctions membres virtuelles ne peuvent pas être template. Si une fonction membre template a le même nom qu'une fonction membre virtuelle d'une classe de base, elle ne constitue pas une redéfinition de cette fonction. Par conséquent, les mécanismes de virtualité sont inutilisables avec les fonctions membres template. On peut contourner ce problème de la manière suivante : on définira une fonction membre virtuelle non template qui appellera la fonction membre template.

Exemple 12-8. Fonction membre template et fonction membre virtuelle

class B
{
    virtual void f(int);
};

class D : public B
{
    template <class T>
    void f(T);     // Cette fonction ne redéfinit pas B::f(int).

    void f(int i)  // Cette fonction surcharge B::f(int).
    {
        f<>(i);    // Elle appelle de la fonction template.
        return ;
    }
};

Dans l'exemple précédent, on est obligé de préciser que la fonction à appeler dans la fonction virtuelle est la fonction template, et qu'il ne s'agit donc pas d'un appel récursif de la fonction virtuelle. Pour cela, on fait suivre le nom de la fonction template d'une paire de signes inférieur et supérieur.

Plus généralement, si une fonction membre template d'une classe peut être spécialisée en une fonction qui a la même signature qu'une autre fonction membre de la même classe, et que ces deux fonctions ont le même nom, toute référence à ce nom utilisera la fonction non-template. Il est possible de passer outre cette règle, à condition de donner explicitement la liste des paramètres template entre les signes inférieur et supérieur lors de l'appel de la fonction.

Exemple 12-9. Surcharge de fonction membre par une fonction membre template

#include <iostream>

using namespace std;

struct A
{
    void f(int);

    template <class T>
    void f(T)
    {
        cout << "Template" << endl;
    }
};

// Fonction non template :
void A::f(int)
{
    cout << "Non template" << endl;
}

// Fonction template :
template <>
void A::f<int>(int)
{
    cout << "Spécialisation f<int>" << endl;
}

int main(void)
{
    A a;
    a.f(1);    // Appel de la version non-template de f.
    a.f('c');  // Appel de la version template de f.
    a.f<>(1); // Appel de la version template spécialisée de f.
    return 0;
}

Pour plus de détails sur la spécialisation des template, voir la Section 12.5.