Accueil
Accueil Le Club Delphi Kylix C C++ Java J2EE DotNET & C# Visual Basic Access Pascal Dev Web PHP ASP XML UML SQLSGBD Windows Linux Autres
logo

precedent    sommaire    suivant   


Qu'est-ce qu'un template ?
auteurs : Aurelien.Regat-Barrel, JolyLoic
Les templates (modèles en français, ou encore patrons) sont la base de la généricité en C++. Il s'agit en fait de modèles génériques de code qui permettent de créer automatiquement des fonctions (dans le cas de fonctions templates) ou des classes (classes templates) à partir d'un ou plusieurs paramètres. Le fait de fournir un paramètre à un modèle générique s'appelle la spécialisation. Elle aboutit en effet à la création d'un code spécialisé pour un type donné à partir d'un modèle générique. Pour cette raison on surnomme aussi les templates des types paramétrés (parameterized types en anglais).
Ces modèles manipulent généralement un type abstrait qui est remplacé par un vrai type C++ au moment de la spécialisation. Ce type abstrait est fourni sous forme de paramètre template qui peut être un type C++, une valeur (entier, enum, pointeur, ...) ou un même un autre template.
La spécialisation d'un template est transparente et invisible. Elle est effectuée lors de la compilation, de manière interne au compilateur, en fonction des arguments donnés au template (il n'y a pas de code source généré quelque part).
Par exemple, vous pouvez réaliser une fonction template renvoyant le plus grand de deux objets de même type pour peu que ce dernier possède un opérateur de comparaison operator > (la fonction standard std::max procède ainsi). Cette fonction template va accepter en argument le type des objets à comparer, appelé type T dans l'exemple suivant :

// renvoie le plus grand entre A et B
template<typename T>
const T & Max( const T & A, const T & B )
{
    return A > B ? A : B;
}
Si vous appelez cette fonction en fournissant deux int, le compilateur va spécialiser la fonction Max pour le type int, ce qui reviendrait à avoir écrit :

const int & Max( const int & A, const int & B )
{
    return A > B ? A : B;
}
Si vous faites de même avec deux float cette fois-ci, une nouvelle spécialisation de la fonction pour le type float va être générée.

const float & Max( const float & A, const float & B )
{
    return A > B ? A : B;
}
Tout se passe comme si vous aviez écrit deux fois la même fonction, une fois pour le type int et une fois pour le type float. Mais vous n'avez bien qu'une seule fonction template Max, qui opère sur un type abstrait déclaré au moyen du mot-clé typename.
Les templates permettent donc de réutiliser facilement du code source, sans devoir utiliser le préprocesseur, ce qui le rend plus lisible et plus rigoureux notamment envers les types manipulés.
Notez qu'il est possible de créer des fonctions membres templates. L'exemple suivant crée une classe permettant de construire une chaîne de caractères au moyen de sa fonction membre template Append.

#include <iostream>
#include <sstream>

class StringBuilder
{
public:
    template<typename T>
    void Append( const T & t )
    {
        this->oss << t;
    }

    std::string GetString() const
    {
        return this->oss.str();
    }

private:
    std::ostringstream oss;
};

int main()
{
    StringBuilder sb;
    sb.Append( 10 );
    sb.Append( '\t' );
    sb.Append( "coucou " );
    sb.Append( 54.12 );
    std::cout << sb.GetString() << '\n';
}
Pour que le code précédent compile sans la fonction membre template, il aurait fallu écrire 4 fonctions membres pour les 4 types utilisés : int, char, const char * et double. Ces 4 fonctions utiliseraient strictement le même code. Grâce à l'utilisation des templates, le compilateur a fait ce travail pour nous. Bien que l'on ait écrit une seule fonction nommée Append, celle-ci n'existe en réalité pas dans le code compilé, mais le compilateur en a généré (spécialisé) quatre. Il en aurait spécialisé dix si dix types différents avaient été utilisés.


Comment créer une fonction template ?
auteur : Aurelien.Regat-Barrel
Prenons comme exemple la fonction suivante qui renvoie le plus grand des deux entiers qui lui sont donnés :

int Max( int A, int B )
{
    return ( A >= B ) ? ( A ) : ( B );
}
Cet exemple est un cas typique de fonction qu'il est intéressant de rendre générique au moyen des templates. Pour cela, il faut s'affranchir du type int que l'on va remplacer par un type abstrait nommé T grâce aux mots clés template et typename:

template<typename T>
T Max( T A, T B )
{
    return ( A >= B ) ? ( A ) : ( B );
}
Notez qu'on aurait pu utiliser des références constantes comme cela est fait dans la fonction standard std::max, mais il s'agit ici d'un exemple.
Le mot clé template indique que la fonction qui suit est une fonction template, et typename dans ce contexte sert à déclarer un nouveau type paramétré pour notre nouvelle fonction template. Il est aussi possible d'utiliser le mot-clé class à la place de typename pour la déclaration des paramètres du template.
Nous venons de créer une fonction template Max possédant un seul type paramétré nommé T. Lorsque nous créons une instance de cette fonction Max de cette manière :

Max<int>( 1, 2 );
Nous demandons au compilateur de spécialiser la fonction Max pour le type int. Ce dernier va en quelque sorte remplacer toutes les occurrences de T par int. Il va d'ailleurs à cette occasion vérifier la validité de l'utilisation de ce type dans le contexte de cette fonction. Avec int pas de problèmes, mais prenons l'exemple suivant:

class Test
{
};

Test a;
Test b;

Max<Test>( a, b );
La classe Test ne possédant pas d'opérateur de comparaison operator >=, la compilation va échouer sur l'utilisation de ce dernier. Les compilateurs récents émettent un message d'erreur assez explicite:

In function `T Max(T, T) [with T = Test]': no match for 'operator>=' in 'A >= B'

ou encore

error C2676: '>=' : 'Test' binaire ne définit pas cet opérateur ou une conversion vers un type acceptable pour l'opérateur prédéfini

Sachez enfin qu'il n'est pas toujours nécessaire de préciser le type du paramètre pour notre template, et que celui-ci peut être déterminé automatiquement par le compilateur (voir Qu'est-ce que la détermination automatique des paramètres templates ?).


Comment créer une classe template ?
auteur : Loulou24
L'écriture de classes templates pose souvent des problèmes de syntaxe ou de conception, voici un exemple illustrant leur écriture:

#include <iostream> 

template <typename T> 
class Exemple 
{ 
public : 

    Exemple(const T& Val = T()); 

    template <typename U> 
    Exemple(const Exemple<U>& Copy); 

    const T& Get() const; 

    template <typename T2> 
    friend std::ostream& operator <<(std::ostream& Stream, const Exemple<T2>& Ex); 

private : 

    T Value; 
}; 

template <typename T> 
Exemple<T>::Exemple(const T& Val) : 
Value(Val) 
{ 

} 

template <typename T> 
template <typename U> 
Exemple<T>::Exemple(const Exemple<U>& Copy) : 
Value(static_cast<T>(Copy.Get())) 
{ 
    // Attention, ceci n'est pas le constructeur par copie ! 
} 

template <typename T> 
const T& Exemple<T>::Get() const 
{ 
    return Value; 
} 

template <typename T> 
std::ostream& operator <<(std::ostream& Stream, const Exemple<T>& Ex) 
{ 
    return Stream << Ex.Value; 
} 

int main() 
{ 
    Exemple<int> A(3); 
    Exemple<float> B(A); 

    std::cout << A << std::endl; 
    std::cout << B << std::endl; 

    return 0; 
}

Qu'est-ce que la spécialisation de template ?
auteur : Loulou24
Une fonction ou une classe template peut être spécialisée pour certains types de paramètres, c'est ce qu'on appelle la spécialisation. Cela permet entre autre d'avoir un comportement spécifique à certains types de paramètres, à des fins d'optimisation ou pour s'adapter à un comportement particulier par exemple. Lors de l'utilisation d'un template avec un type donné, le compilateur recherche s'il existe une spécialisation du template pour ce type. S'il en trouve une il utilise cette version spécialisée, sinon il se rabat sur la version générique de base du template. On peut spécialiser une fonction, une fonction membre template de classe, ou une classe toute entière.
Voici la syntaxe à utiliser pour effectuer une spécialisation (attention l'ordre est important : la version générique doit apparaître en premier) :

// Version générique 
template <typename T>
void QuiSuisJe( const T & x ) 
{ 
    std::cout << "Je ne sais pas" << std::endl; 
} 

// Spécialisation pour les int 
template <>
void QuiSuisJe<int>( const int & x ) 
{ 
    std::cout << "Je suis un int" << std::endl; 
} 

// Spécialisation pour ma classe 
template <>
void QuiSuisJe<MaClasse>( const MaClasse & x ) 
{ 
    std::cout << "Je suis un MaClasse" << std::endl; 
} 

// Test 
MaClasse Test1; 
int Test2; 
float Test3; 

QuiSuisJe( Test1 ); // "Je suis un MaClasse" 
QuiSuisJe( Test2 ); // "Je suis un int" 
QuiSuisJe( Test3 ); // "Je ne sais pas"
La spécialisation de classe est elle plus contraignante car il faut redéfinir la totalité de celle-ci.

template<typename T>
struct Modele 
{ 
    void QuiSuisJe() 
    { 
        std::cout << "Je suis un Modele<inconnu>" << std::endl; 
    } 
}; 

template<>
struct Modele<int> 
{ 
    void QuiSuisJe() 
    { 
        std::cout << "Je suis un Modele<int>" << std::endl; 
    } 

    void CestQuoiCetteFonction() 
    { 
        // On peut tout à fait ajouter des fonctions
        // en fait le contenu de la classe peut être totalement différent ! 
    } 
}; 

Modele<float> M1; 
Modele<int> M2; 

M1.QuiSuisJe(); // "Je suis un Modele<inconnu>" 
M2.QuiSuisJe(); // "Je suis un Modele<int>" 

M1.CestQuoiCetteFonction(); // Erreur : 'fonction inconnnue' 
M2.CestQuoiCetteFonction(); // OK

Qu'est-ce que la détermination automatique des paramètres templates ?
auteur : Loulou24
Lorsque vous appelez une fonction template, vous n'avez pas toujours besoin d'indiquer explicitement le type de vos paramètres templates : le compilateur est souvent capable de le faire pour vous.

template <typename T>
void Fonction( T x ) 
{ 
} 

Fonction<double>(5.2f); 
// Equivalent à 
Fonction( 5.2 );
Ceci n'est pas toujours possible, il existe certaines situations où l'on est obligé de spécifier explicitement le type des paramètres manipulés (lorsque le compilateur ne peut les déduire ou bien pour lever une ambiguïté par exemple).

template <typename T>
T Fonction()
{
    return T(); // renvoie le type 
} 

int x = Fonction(); // Erreur : 'impossible de déduire l'argument de modèle' 
int x = Fonction<int>(); // OK
template <typename T>
void Fonction( T x1, T x2 ) 
{ 
} 

int x1 = 5; // premier argument de type int
double x2 = 6.5; // second argument de type double

Fonction( x1, x2 ); // Erreur : 'paramètre ambigu' 
Fonction<double>( x1, x2 ); // OK
Fonction( static_cast<double>( x1 ), x2 ); // OK
La détermination automatique des paramètres ne peut s'appliquer que sur des fonctions templates. Pour les classes templates il faut systématiquement les expliciter.


Pourquoi mes templates ne sont-ils pas reconnus à l'édition des liens ?
auteurs : LFE, haypo
Contrairement aux classes 'normales', les templates ne peuvent pas avoir leur interface définie dans un fichier d'en-tête et leur implémentation dans un fichier .cpp.
Il faut absolument que le template soit entièrement défini dans le fichier d'en-tête (.h).

Une autre façon de faire permet de garder la logique du .h contenant l'interface et le .cpp contenant l'implémentation est la suivante :

// le .h 
#ifndef MON_TEMPLATE_H 
#define MON_TEMPLATE_H 

template <class T> 
class Liste { ... }; 

#include "mon_template.cpp" 
#endif
 
// le .cpp
#ifdef MON_TEMPLATE_H // évite les erreurs de compilation ;-) 

template <class T> 
Liste<T>::Liste () { ... } 

#endif

A quoi sert le mot-clé typename ?
auteurs : Loulou24, Aurelien.Regat-Barrel, Luc Hermitte
Le mot-clé typename sert à introduire des identificateurs dont le type est inconnu dans le contexte des templates. On l'utilise en particulier pour manipuler un type qui est défini à l'intérieur d'un autre type que l'on reçoit comme paramètre template.

#include <list> 

template <typename T> // à priori, on ne sait rien du type T
class MaClasse 
{ 
public:
    typedef std::list<T>::iterator Iter; // Erreur avec certains compilateurs (ou warning, ou rien)
    typedef typename std::list<T>::iterator Iter2; // Ok 
};
Bien que l'exemple ci-dessus compile sur certains compilateurs sans avoir recours à typename, le standard exige sa présence, et les compilateurs modernes vont dans ce sens. Il convient donc de l'utiliser même si votre compilateur sait s'en passer.
typename peut aussi être utilisé au même titre que le mot-clé class pour déclarer les types paramétrés des templates (typename T pourrait être remplacé par class T). L'utilisation ou non de typename dans ce contexte n'est qu'une question de style.


Peut-on créer un alias (typedef) sur des templates ?
auteur : Aurelien.Regat-Barrel
Malheureusement non, dans le standard C++ actuel on ne peut pas écrire quelque chose comme cela :

#include <vector>

typedef std::vector Tableau; // erreur de compilation

Tableau<int> t;
Une solution qui peut parfois convenir consiste à utiliser une struct template:

#include <vector>

template <typename T>
struct Tableau
{
    typedef std::vector<T> type;
};

Tableau<int>::type t;
lien : fr.comp.lang.c++

precedent    sommaire    suivant   

Consultez les autres F.A.Q's

Valid XHTML 1.1!Valid CSS!


Ce document issu de http://www.developpez.com est soumis à la licence GNU FDL traduit en français ici.
Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement.
Certaines parties de ce document sont sous copyright Marshall Cline Les codes sources présentés sur cette page sont libres de droits, et vous pouvez les utiliser à votre convenance. Pour le reste, ce document constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Ce document issu de http://www.developpez.com est soumis à trois licences, en fonction des contributeurs :
- Les contributions de Clément Cunin et LFE sont soumises aux termes de la la licence GNU FDL traduite en français ici. Permission vous est donnée de distribuer, modifier des copies des contributions de Clément Cunin et LFE tant que cette note apparaît clairement :
"Ce document issu de http://www.developpez.com est soumis à la licence GNU FDL traduite en français ici. Permission vous est donnée de distribuer, modifier des copies de cette page tant que cette note apparaît clairement".
- Les contributions de Marshall Cline sont sous copyright
- Pour ce qui est des autres contributions : Copyright © 2005 Developpez LLC : Tous droits réservés Developpez LLC. Aucune reproduction, ne peut en être faite sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.
Vos questions techniques : forum d'entraide Accueil - Publiez vos articles, tutoriels, cours et rejoignez-nous dans l'équipe de rédaction du club d'entraide des développeurs francophones. Nous contacter - Copyright 2000..2005 www.developpez.com