Guru Of the Week n° 49 : spécialisation et surcharge de templates

Difficulté : 6 / 10
QComment spécialiser et surcharger les templates ? Quand le faire ? Comment déterminer quel template est appelé ? Faites-vous la main en analysant ces douze exemples.

Retrouver l'ensemble des Guru of the Week sur la page d'index.

N'hésitez pas à commenter cet article ! Commentez Donner une note à l'article (0)

Article lu   fois.

Les deux auteurs

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Problème

I-A. Questions JG

1. Qu'est-ce que la spécialisation de template ? Donnez un exemple.

2. Qu'est ce que la spécialisation partielle ? Donnez un exemple.

I-B. Question Guru

3. Considérez les déclarations suivantes :

 
Sélectionnez
    template<typename T1, typename T2>
    void f( T1, T2 );                       // 1
    template<typename T> void f( T );       // 2
    template<typename T> void f( T, T );    // 3
    template<typename T> void f( T* );      // 4
    template<typename T> void f( T*, T );   // 5
    template<typename T> void f( T, T* );   // 6
    template<typename T> void f( int, T* ); // 7
    template<> void f<int>( int );          // 8
    void f( int, double );                  // 9
    void f( int );                          // 10

Lesquelles des fonctions ci-dessus sont invoquées par chacune des suivantes ? Soyez spécifique en identifiant les types de paramètres des templates, quand il faut.

 
Sélectionnez
    int             i;
    double          d;
    float           ff;
    complex<double> c;

    f( i );         // a
    f<int>( i );    // b
    f( i, i );      // c
    f( c );         // d
    f( i, ff );     // e
    f( i, d );      // f
    f( c, &c );     // g
    f( i, &d );     // h
    f( &d, d );     // i
    f( &d );        // j
    f( d, &i );     // k
    f( &i, &i );    // l

II. Solution

Les templates fournissent la forme la plus puissance de caractère générique de C++. Ils vous permettent d'écrire des codes génériques qui fonctionnent avec de nombreux types d'objets sans rapports entre eux, par exemple des chaînes qui contiennent différents types de caractères, des containers qui peuvent contenir des types arbitraires d'objets, et des algorithmes qui peuvent fonctionner sur des types arbitraires de séquences.

II-A. 1. Qu'est-ce que la spécialisation de template ? Donnez un exemple.

La spécialisation de template permet aux templates de traiter de cas particuliers. Parfois, un algorithme générique peut travailler beaucoup plus efficacement pour un certain type de séquence (par exemple quand on lui donne des itérateurs à accès libre), aussi est-il rationnel de le spécialiser pour ce cas tout en utilisant une approche plus lente mais plus générique pour tous les autres cas. Les performances sont une raison courante à la spécialisation, mais ce n'est pas la seule ; par exemple, vous pourriez aussi spécialiser un template pour travailler avec certains objets qui ne se conforment pas à l'interface normale attendue par le template générique.

Ces cas particuliers peuvent être traités en utilisant deux formes de spécialisation de template : la spécialisation explicite et la spécialisation partielle.

II-A-1. Spécialisation explicite

La spécialisation explicite vous permet d'écrire une implémentation spécifique à une combinaison particulière de paramètres de template. Par exemple, étant donné le template de fonction :

 
Sélectionnez
  template<class T> void sort(Array<T>& v) { /*...*/ };

Si nous avions une méthode plus rapide (ou spécialisée autrement) avec laquelle nous voudrions traiter spécifiquement les ensembles de caractères, nous pourrions spécialiser explicitement :

 
Sélectionnez
  template<> void sort<char*>(Array<char*>&);

Le compilateur choisira alors le template le plus approprié :

 
Sélectionnez
  Array<int>   ai;
  Array<char*> apc;

  sort( ai );       // appelle sort<int>
  sort( apc );      // appelle un sort<char*> spécialisé

II-B. Qu'est ce que la spécialisation partielle ? Donnez un exemple.

II-B-1. Spécialisation partielle

Pour les templates de classes uniquement, vous pouvez définir des spécialisations partielles qui n'auront pas à traiter tous les paramètres des templates de classes primaires (non spécialisés).

Voici un exemple issu de 14.5.4 [temp.class.spec]. Le premier template est le template de classe primaire :

 
Sélectionnez
  template<class T1, class T2, int I>
  class A             { };             // #1

Nous pouvons le spécialiser pour ce cas quand T2 est un T1* :

 
Sélectionnez
  template<class T, int I>
  class A<T, T*, I>   { };             // #2

ou pour le cas où T1 est un pointeur, quel qu'il soit :

 
Sélectionnez
  template<class T1, class T2, int I>
  class A<T1*, T2, I> { };             // #3

ou pour le cas où T1 est un entier, T2 est un pointeur et I est égal à 5 :

 
Sélectionnez
  template<class T>
  class A<int, T*, 5> { };             // #4

ou pour le cas où T2 est un pointeur :

 
Sélectionnez
  template<class T1, class T2, int I>
  class A<T1, T2*, I> { };             // #5

Les déclarations 2 à 5 déclarent des spécialisations partielles du template primaire. Le compilateur choisira alors le template approprié. à partir de 14.5.4.1 [temp.class.spec.match] :

 
Sélectionnez
  A<int, int, 1>   a1;  // utilise #1
  A<int, int*, 1>  a2;  // utilise #2, T est un entier,
                        //             I = 1
  A<int, char*, 5> a3;  // utilise #4, T est un caractère,
  A<int, char*, 1> a4;  // utilise #5, T1 est un entier,
                        //             T2 est un caractère,
                        //             I = 1
  A<int*, int*, 2> a5;  // ambigu :
                        // correspond à #3 et #5

II-B-2. Surcharge de template de fonction

à présent, considérons la surcharge de template de fonction. Ce n'est pas la même chose que la spécialisation, mais ça y est lié.

C++ vous permet de surcharger les fonctions, toutefois, assurez-vous que c'est la bonne chose qui est appelée :

 
Sélectionnez
  int  f( int );
  long f( double );

  int    i;
  double d;

  f( i );   // appelle f(int)
  f( d );   // appelle f(double)

De façon similaire, vous pouvez surcharger des templates de fonctions, ce qui nous amène à la question finale :

II-C. Considérez les déclarations suivantes :

 
Sélectionnez
    template<typename T1, typename T2>
    void f( T1, T2 );                       // 1
    template<typename T> void f( T );       // 2
    template<typename T> void f( T, T );    // 3
    template<typename T> void f( T* );      // 4
    template<typename T> void f( T*, T );   // 5
    template<typename T> void f( T, T* );   // 6
    template<typename T> void f( int, T* ); // 7
    template<> void f<int>( int );          // 8
    void f( int, double );                  // 9
    void f( int );                          // 10

D'abord, simplifions un peu les choses en notant qu'il y a là deux groupes de f surchargés : celles avec un unique paramètre, et celles qui prennent deux paramètres [Note : j'ai délibérément évité de troubIer le jeu en incluant une surcharge avec deux paramètres là où le second paramètre avait une valeur par défaut. S'il y avait eu une fonction dans ce genre, alors pour déterminer l'ordre correct, il aurait fallu considérer la fonction dans les deux listes : une fois comme une fonction à un seul paramètre (utilisant la valeur par défaut), et une fois comme une fonction à deux paramètres (n'utilisant pas la valeur par défaut)].

 
Sélectionnez
    template<typename T1, typename T2>
    void f( T1, T2 );                       // 1
    template<typename T> void f( T, T );    // 3
    template<typename T> void f( T*, T );   // 5
    template<typename T> void f( T, T* );   // 6
    template<typename T> void f( int, T* ); // 7
    void f( int, double );                  // 9

    template<typename T> void f( T );       // 2
    template<typename T> void f( T* );      // 4
    template<> void f<int>( int );          // 8
    void f( int );                          // 10

Maintenant, considérons chacun des appels tour à tour :

Lesquelles des fonctions ci-dessus sont invoquées par chacune des suivantes ? Soyez spécifique en identifiant les types de paramètres des templates, quand il faut.

 
Sélectionnez
    int             i;
    double          d;
    float           ff;
    complex<double> c;

    f( i );         // a

A. appelle #10, parce que c'est une correspondance exacte avec #10 et ce genre de non-templates est toujours préféré aux templates (cf. 13.3.3).

 
Sélectionnez
    f<int>( i );    // b

B. appelle #8, parce qu'il est explicitement fait appel à f<int>.

 
Sélectionnez
    f( i, i );      // c

C. appelle #3 (T est un entier), parce que c'est la meilleure correspondance.

 
Sélectionnez
    f( c );         // d

D. appelle #2 (T est un complex<double>), parce qu'aucun autre f ne peut correspondre.

 
Sélectionnez
    f( i, ff );     // e

E. appelle #1 (T1 est un entier, T2 est en virgule flottante). Vous pourriez penser que #9 en est très proche - et c'est le cas - mais une fonction non-template n'est préférée que si c'est une correspondance exacte.

 
Sélectionnez
    f( i, d );      // f

F. appelle #9, parce qu'à présent #9 est une correspondance exacte et qu'alors la fonction non-template sera préférée.

 
Sélectionnez
    f( c, &c );     // g

G. appelle #6 (T est un complex<double>), parce que #6 est la surcharge la plus proche. #6 fournit une surcharge de f là où le second paramètre est un pointeur du même type que le premier paramètre.

 
Sélectionnez
    f( i, &d );     // h

H. appelle #7 (T est un double), parce que #7 est la surcharge la plus proche.

 
Sélectionnez
    f( &d, d );     // i

I. appelle #5 (T est un double). #5 fournit une surcharge de f là où le premier paramètre est un pointeur du même type que le second paramètre.

Juste quelques-uns encore...

 
Sélectionnez
    f( &d );        // j

J. Clairement (maintenant), nous appelons #4 (T est un double).

 
Sélectionnez
    f( d, &i );     // k

K. Plusieurs autres surcharges en sont proches, mais seul #1 correspond ici (T1 est un double, T2 est un int*).

Et finalement...

 
Sélectionnez
    f( &i, &i );    // l

L. appelle #3 (T est un int*), qui est la plus proche surcharge, bien que quelques autres mentionnent explicitement un paramètre pointeur.

Les bonnes nouvelles :

Les vendeurs de compilateurs supporteront bientôt mieux les templates, de sorte que vous pourrez utiliser des caractéristiques telles que celles ci-dessus de façon plus fiable et portable.

Les mauvaises nouvelles :

Si vous avez répondu juste à toutes les questions ci-dessus, vous connaissez probablement les règles mieux que votre compilateur actuel.

III. Remerciements

Cet article est une traduction en français par l'équipe de la rubrique C++ de l'article de Herb Sutter publié sur Guru of the Week. Vous pouvez retrouver cet article dans sa version originale sur le site de Guru of the Week : Template Specialization and Overloading.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2009 Herb Sutter. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.