Guru Of the Week n° 35 : le mot-clé typename

| Herb Sutter | Traduction par l'équipe de la rubrique C++ |

Difficulté : 9,5 / 10
« Qu'est-ce qu'un nom (de type) ? » Voici un exercice qui démontre pourquoi et comment utiliser typename, en utilisant un idiome courant dans la bibliothèque standard.

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

N'hésitez pas à commenter cet article ! Commentez

- Titre : Guru Of the Week n° 35 : le mot-clé typename

- Auteurs : Herb Sutter, Traduction par l'équipe de la rubrique C++

- Parution : 9 juillet 2012

- Licence : 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.

Sommaire

- I. Problème

- I-A. Question Guru

- II. Solution

- II-A. Utilisation de typename pour des noms dépendants

- II-B. Le point secondaire (et subtil)

- II-C. Plaisanterie

- III. Remerciements

I - Problème

I-A - Question Guru

Y a-t-il quelque chose de mauvais dans le code ci-dessus ? Si oui, quoi ?

template<class T>
struct X_base {
typedef T instantiated_type;
};

template<class A, class B>
struct X : public X_base<B> {
bool operator()( const instantiated_type& i ) {
return ( i != instantiated_type() );
}

// ... d'autres choses ...

};

II - Solution

Y a-t-il quelque chose de mauvais dans le code ci-dessus ? Si oui, quoi ?

Cet exemple illustre le problème de pourquoi et comment utiliser typename pour se référer à des noms dépendants et peut apporter un éclairage à la question : « qu'est-ce qu'un nom ? »

II-A - Utilisation de typename pour des noms dépendants

Le problème avec X est que instantiated_type est censé se référer au typedef supposé hérité de la classe de base X_base<B>. Malheureusement, au moment où le compilateur doit analyser la définition inline de X<A,B>::operator()(), les noms dépendants (c'est-à-dire les noms qui dépendent des paramètres de template, comme le nom hérité X_Base::instantiated_type) ne sont pas visibles, aussi le compilateur se plaindra-t-il de ne pas savoir ce que instantiated_type est censé signifier. Les noms dépendants n'apparaîtront que plus tard, au moment où le template sera effectivement instancié.

Si vous vous demandez pourquoi le compilateur n'a pas pu comprendre, imaginez que vous êtes un compilateur et demandez-vous comment comprendre ce que instantiated_type signifie ici. À la dernière ligne, vous ne pouvez pas comprendre, parce que vous ne savez pas encore ce qu'est B, ni si plus tard il n'y aura pas une spécialisation pour X_base<B> qui ne fera pas de X_base<B>::instantiated_type quelque chose d'inattendu – un nom de type ou même une variable membre. Dans le template non spécialisé X_base ci-dessus, X_base<T>::instantiated_type sera toujours T, mais rien ne peut empêcher quelqu'un de changer cela pour une spécialisation, par exemple :

template<>
struct X_base<int> {
typedef Y instantiated_type;
};

Accepté, le nom du typedef induirait un peu en erreur, mais c'est légal. Ou même :

template<>
struct X_base<double> {
double instantiated_type;
};

À présent, le nom induit moins en erreur, mais le template X ne peut pas fonctionner avec X_base<double> comme une classe de base parce que instantiated_type est une variable membre, pas un nom de type.

À la dernière ligne, le compilateur ne saura pas comment analyser la définition de X<A,B>::operator()() à moins que nous disions ce qu'est instantiated_type… au minimum si c'est un type ou autre chose. Ici, nous voulons que ce soit un type.

La façon de dire au compilateur que quelque chose comme cela est censé être un nom de type consiste à entrer le mot-clé typename. Pour nous, il y a deux façons de s'y prendre : la moins élégante consiste à simplement écrire typename partout où nous nous référons au instantiated_type :

template<class A, class B>
struct X : public X_base<B> {
bool operator() ( const typename X_base<B>::instantiated_type& i ) {
return ( i != typename X_base<B>::instantiated_type() );
}

// ... d'autres choses ...
};

J'espère que vous avez tressailli en lisant cela. Comme d'habitude, les typedef rendent ce genre de choses beaucoup plus lisibles et en fournissant un autre typedef, le reste de la définition fonctionne comme il a été écrit à l'origine :

template<class A, class B>
struct X : public X_base<B> {
typedef typename X_base<B>::instantiated_type instantiated_type;

bool operator()( const instantiated_type& i ) {
return ( i != instantiated_type() );
}

// ... d'autres choses ...
};

Avant d'aller plus loin dans la lecture, y a-t-il quoi que ce soit qui vous paraisse inhabituel concernant l'ajout de ce typedef ?

II-B - Le point secondaire (et subtil)

J'aurais pu utiliser des exemples plus simples pour illustrer cela (plusieurs apparaissent dans la norme au chapitre 14.6.2), mais cela n'aurait pas fait ressortir la chose inhabituelle : la seule raison d'être de la base vide X_base semble être de fournir le typedef. Toutefois, les classes dérivées finissent habituellement simplement par un nouveau typedef.

Cela ne semble-t-il pas redondant ? Ça l'est, mais seulement un petit peu… Après tout, c'est toujours la spécialisation de X_base<> qui est responsable de la détermination de ce que le type approprié doit être, et ce type peut changer pour différentes spécialisations.

La bibliothèque standard contient des classes de base comme ceci : « bags-o-typedefs ». Elles sont destinées à être utilisées d'une seule façon. En espérant que ce sujet de GotW aidera à prévenir certaines questions concernant pourquoi des classes dérivées « re-typedef-ent » ces typedef, de façon apparemment redondante et à montrer que cet effet n'est pas vraiment une défaillance de la conception de langage et qu'il est juste une autre facette de cette question vieille comme le monde : « Qu'est-ce qu'un nom ? »

II-C - Plaisanterie

En bonus, voici une petite plaisanterie :

#include <iostream>
using std::cout;
using std::endl;

struct Rose {};

struct A { typedef Rose rose; };

template<class T>
struct B : T { typedef typename T::rose foo; };

template<class T>
void smell( T ) { cout << "awful" << endl; }

void smell( Rose ) { cout << "sweet" << endl; }

int main() {
smell( A::rose() );
smell( B<A>::foo() );
}

:-)

III - Remerciements

Cet article est une traduction 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 : Typename.

Merci à Luc Hermitte pour sa relecture technique et à ClaudeLELOUP pour sa relecture orthographique.