Templates


Les patrons du c++ permettent de fabriquer des fonctions et des classes paramétrées par des types.

Pourquoi des patrons, car le c++ ne propose pas une vrai édition de lien dynamique car il est fortement typé, que la fabrication de méta-classes est bloquée par la nécessaire compilation du code. Les patrons sont une réponse mal commode à ces manques.

Le plus grave étant l'absence de protocoles (objectif C) ou d'interfaces (Java) qui permettent de faire un contrôle dynamique de la classe utilisée dans le code générique.

Les patrons sont une conséquence du typage fort.


L'objectif des patrons est la réutilisation d'algorithmes, la réutilisation de concepts se fait par héritage.

Par exemple : la fonction minimum de deux entiers

int min (int a, int b)
    {return a < b ? a : b ;}
est similaire au minimum de 2 doubles.
Double min (double a, double b)
     {return a < b ? a : b ;}
La solution du c une macro
# define min (a,b) ((a)<(b)?(a):(b))
qui a le défaut et l'avantage d'être non typé
La solution c++ un patron de fonction " fonction template ".

// Une fonction de template
# include <iostream.h>
template <class TYPE_COMPARABLE>
TYPE_COMPARABLE maximum (TYPE_COMPARABLE a,
TYPE_COMPARABLE b)
{
     return (a > b) ? a : b ;
}
 
void main (void)
{
int x = 12, y = -7 ;
float real = 3.1415 ;
char ch = 'A' ;
cout
 
 
 
 
 

  ;
}

<< "Resultats:" << endl
<< maximum (x,y) << endl
<< maximum (-34,y) << endl
<< maximum (real, float (y))<< endl
<< maximum (real, float (x))<< endl
<< maximum (ch,'x') << endl
 
// Resultats :
// 12
// -7
// 3.141
// 12.000
// x
Création des trois fonctions (instanciations) à la compilation.

Définition d'un patron de fonction

Le mot clef template précède toute déclaration et définition, une liste de paramètres formels suit ce mot clef.

Dans l'exemple la liste est <class TYPE_COMPARABLE> il est possible d'avoir une liste plus longue template <class Automates,class Etat,class Transition>.

Dans l'exemple l'identificateur de type TYPE_COMPARABLE peut-être utilisé dans la portée du template qui est égale à celle de la fonction ou de la classe décrite par le template.

Chaque identificateur de type ne peut apparaître qu'une fois dans la liste des paramètres formels.


Quelques exemples de parametrage de types

    // déclaration
template <class t_comparable>
t_comparable min (type*,int); // min d'un tableau
    // instanciations
int ival ;
     min (ival,3);//erreur n'est pas de type pointeur
const int icval = 10 ;
     min (& icval,1); //erreur const int* et pas int*

    // Deuxième version
template <class t_comparable>
t_comparable min (const t_comparable *, int) ;
    min (& ival,1) ; //ok conversion triviale
    min (& icval,1); //ok
    min (& ival,o) ; //t_comparable º int
    min (argv,argc); //t_comparable º char *
    min (argc,argc); //erreur
      template <class T> T min (T, T) ;
          min (10 u,10);  // erreur unsigned int º¤ int
          min (argv,10 u);// erreur deux paramètres unsigned
                          // les promotions non triviales
                          // ne sont pas faites
    // avec un cast
    min (argv,(int)10 u); //ok

Surcharge d'un patron de fonction

Notre template min fonctionne avec les types prédéfinis comparables et les types utilisateurs définisant l'opérateur <, mais pour le type char * le template ne nous fournit pas le bon opérateur de plus il n'est pas possible de redéfinir l'opérateur < pour les char *.

La solution consiste à surcharger le template c-a-d écrire explicitement la version pour les char*.

Char * min (char * a, char * b)
    {return (strcmp(a,b)< o ? a : b);}
Attention il faut aussi écrire :
    const char* min(const char* a,const char* b)
    {. . .             }

Ambiguïtés et signatures

Attention de ne pas faire de surcharge entre template :
     template <class T> int max (T, T) ;
et
   template <class U> U max ( U, U) ;
erreur les deux templates ont la même signature c-a-d les deux fonctions ont des paramètres de même type.

Dans le cas suivant notre fonction vérifie une association :

template <class T> Asso (T,T);
mais
     Asso (unsigned)o,(int)o); //erreur
d'où l'écriture :
template <class T, class u> ? Asso (T,U);
mais avec quelle valeur de retour comme la signature ne contient pas la valeur de retour nous ne pouvons pas écrire 2...

Une solution addoc un paramètre de typage :

template <class U, class T, class RT>
     RT Asso (U,T,RT) ;

Algorithme de résolution d'un template

Quand le compilateur détecte un appel de fonction il suit l'algorithme suivant pour associer la bonne fonction.
  1. Dans les fonctions non templates ; nombre d'associations exactes
 
a. une seule
b. plusieurs
c. zéro
ok ® terminé
erreur ® terminé 
® (2)
 
  1. Template, nombre d'associations exactes
 
a. une seule
b. plusieurs
c. zéro
ok ® terminé
erreur ® terminé 
® (3)
 
  1. Réexaminer les non templates de façons classiques avec d'éventuelles conversions de type (standard sur utilisateur).

L'instanciation de template

L'instanciation de template, c-a-d, la fabrication de la fonction pour un type donné, est la première forme syntaxique qui demande plus "d'intelligence" de l'environnement que ce que l'on trouve d'habitude sur un système UNIX.

En effet, il faut que le compilateur ET le linker s'assure que l'on trouvera une et une seule copie de chaque instanciation nécessaire et AUCUNE instanciation inutile.

Deux approches :

  1. un linker "intelligent" différent des linker standards qui va assurer l'unité des instanciations (solution P.C.).
  2. Conserver le linker ordinaire. Mais définir un "code intermédiaire" spécifique aux templates qui sera instancié au moment de l'édition de liens (Solution A T & T).
Le défaut de la première méthode est de compiler plusieurs fois le même template. L'avantage est de ne travailler qu'avec des fichiers ".o".

La deuxième solution se conjugue de plusieurs façons :
 
HPCC un répertoire spécial ./ptrepository contient le code intermédiaire, ce code intermédiaire permet de générer les instances nécessaires à la compilation.
cc  d'autres versions chez d'autres constructeurs.
Enfin g++ ne propose pas d'instanciation automatique de template. (cf - Patrons 13).

Avec HPCC on peut donc utiliser de la compilation séparée de template ! (mais ce n'est pas portable ? ).


Instanciation de template que faire avec g++ ?

- Trois solutions :
 
.Brutale  - ne rien faire - les templates sont inclus
     partout ®  ! temps de compilation
! taille d'exécutable 
 
.L'option - f external - template
on utilise deux pragmas de compilation :
# pragma interface
dans les fichiers contenant des définitions de template.
# pragma implémentation
dans le fichier dans lequel on veut que l'instanciation
soit réalisée (attention : un seul fichier).
.L'option - f no - implicit - template
il faut demander explicitement l'instanciation avec le type que vous voulez, avec la syntaxe suivante :
           # include "Foo.h"
           # include "Foo.C"
           template class Foo <int> ;
instanciation explicite de même pour une fonction.

Patron de classes

template <class T> class file ;
Il est possible d'avoir des paramètres expressions (ou le type est instancié).
Template <class T, int> class tableau ;
Dans le template le nom du template permet de parler de toutes ces instances :
 
template <class Type>
class MaillonFile {  
 
public :
MaillonFile (const Type &) ;

} ;

private :

Type element ;
MaillonFile * suivant ;

 
Création de l'instance du patron et d'une instance de cette instance :
 
  //instance MaillonFile <int>
MaillonFile <int> fe (103) ;
Chaîne s ("coucou") ;
MaillonFile <chaîne> fc (s) ;
//avec fe comme instance.
s, fe et fc sont des instances "classiques"
 
Z   efficacité : Maillon File (const Type & x) : element (x) {  }

Un patron pour List et List Element

template <class Type>
class list Element {...
      template <class T> friend class List ;
} ;
Toutes les classes List sont amies de toutes les classes List Elément !
template <class type>
Class List Element {...
      friend class list <Type> ;
} ;
Définition de << sur la class List
 
template <class T>
ostream & operator << (ostream & as, list <T> & e)
{as << "< " ;
      list Element <T> * p ;
      for(p = e ® début ( ) 

; p ; p = p ® suivant)

 

         as << *p << ", " ;
  as << ">" ;
 return as ;
 }

//de légation de << sur 
//list Element 
 
il faut que l'opérateur soit ami de la class list
template <class T> class list { ...
   friend ostream & operator << (ostream &,list <T> &);
...
};
Nous devons faire de même pour List Element, on écrira dans l'opérateur
as << p. element ;
Si l'opérateur << n'est pas défini pour le type avec lequel vous avez instancié le template le compilateur vous signalera une erreur.

Z  Mais attention si vous n'utilisez pas << sur une liste définie pour ce type le compilateur ne dira rien !
       Ce qui est un défaut comme une qualité !


Spécialisation de template ou encore des problèmes avec char *

Notre template de liste ne fonctionne pas avec char * car il faut allouer la zone de stockage d'où la définition d'un constructeur spécifique
     typedef char * p char;
List Element <p char> :: list Element (const p char & p)
{
     element = new char [s tr l en (p) + 1];
     assert (element);
     s trcp y (element, p);
     suivant = NULL ;
}
il est aussi possible de spécifier la classe
List <p char> en entier.

template sur expressions constantes

template <class T, int size>

class tableau { ...
           private :
                   T data [size];
   ...
} ;

tableau <int,10> t ;//tableau de 10 int!


Des membres statiques dans un template

Nous voulons dans la classe liste conserver les maillons désalloués ou faire de l'allocation par block, il nous faut un pointeur statique sur la réserve : template <class Type>
class list Element {...
                    static List Element <Type> * reserve ;
                    static const int taille-block ;
               ...
};
template <class T>
List Element <T> * list Element <T> :: reserve = NULL ;
template <class T>
const int list Element <T> :: taille-block = 24 ;
const int list Element <char *> :: taille-block = 1024 ;

L'ami du patron

  1. fonctions standards amies
  2. Patrons amis liés
  3. Patrons amis non liés
template <class Z>
class X { Z val ;
friend foo ( ); //1
friend salad boeuf <Z> ; //2
friend saussice frite <Z>( ); //2
friend delta : : gamma (X <Z>); //2
template <class T> friend class toto ; //3
template <class T> friend void bak ( X <T>) ; //3
template <class T> friend void g <T> : : g ( ) ; //3
};