template et typename en C++ : où les placer et pourquoi ils sont indispensables
\n\nSi vous avez déjà écrit ou lu du code C++ avec des templates, vous avez sûrement rencontré une situation où le compilateur affiche soudainement une erreur alors que tout semble correct. Vous êtes certain que le code est bon, mais le compilateur insiste pour que vous ajoutiez template ou typename. Cela vous dit quelque chose ? Bienvenue au club.
Cette question est l'une des plus populaires sur Stack Overflow (des milliers de vues et des dizaines de réponses). Et ce n'est pas surprenant : même les développeurs expérimentés confondent parfois l'utilisation de ces mots-clés. Dans cet article, nous allons clarifier une bonne fois pour toutes : où, quand et pourquoi écrire template et typename en C++. Et surtout, nous verrons comment éviter les pièges courants.
Pourquoi ce problème existe-t-il ?
\n\nLa racine du mal réside dans les noms dépendants (dependent names). Lorsque vous écrivez du code template, le compilateur ne peut pas déterminer immédiatement s'il a affaire à un type ou à autre chose. Cela est dû au fait que les paramètres du template ne sont pas encore connus. Le compilateur reporte l'analyse jusqu'à l'instanciation, mais il doit connaître le rôle syntaxique du nom dès la phase d'analyse initiale.
\n\nImaginez que vous écrivez :
\n\ntemplate<typename T>\nvoid foo() {\n T::something * p;\n}\n\n\nQu'est-ce que cela signifie ? Une déclaration de pointeur p vers T::something ? Ou une multiplication de T::something par p ? Sans information supplémentaire, le compilateur ne peut pas le savoir. C'est précisément pour résoudre cette ambiguïté que les mots-clés typename et template ont été introduits.
Le mot-clé typename : quand le type n'est pas évident
\n\nRègle de base
\n\nLe mot-clé typename est utilisé pour indiquer au compilateur : "Hé, ce qui suit est un type, pas autre chose". Il est nécessaire lorsque le nom dépend d'un paramètre de template et que le compilateur ne peut pas déterminer seul qu'il s'agit d'un type.
Prenons un exemple classique :
\n\ntemplate<typename T>\nclass MyClass {\npublic:\n // Sans typename — erreur de compilation\n typename T::value_type getValue() const {\n return data;\n }\nprivate:\n T data;\n};\n\n\nIci, T::value_type est un nom dépendant. Sans typename, le compilateur penserait que value_type est un membre de données statique ou une classe imbriquée, mais pas un type. Pourtant, nous savons qu'il s'agit d'un type (par exemple, int, si T est std::vector<int>).
Où typename est-il encore nécessaire ?
\n\nLa règle est simple : typename est obligatoire devant les noms de types dépendants dans les contextes suivants :
- \n
- Dans les déclarations de variables et de membres de classe \n
- Dans les paramètres de fonctions \n
- Dans les types de retour \n
- Dans les déclarations using (alias de types) \n
- Dans les classes de base (si la classe de base est un type dépendant) \n
Exemple avec using :
\n\ntemplate<typename T>\nclass Container {\n using Iterator = typename T::iterator; // typename obligatoire !\n // ...\n};\n\n\nQuand typename n'est-il PAS nécessaire ?
\n\nIl existe des exceptions importantes où typename n'est pas requis :
- \n
- Dans la liste des paramètres du template :
template<typename T>— icitypenamefait partie de la syntaxe \n - Dans les classes de base :
class Derived : public Base<T>::Nested— icitypenamen'est pas nécessaire \n - Dans la liste d'initialisation du constructeur \n
- Dans un contexte où le nom est explicitement un type (par exemple, après
new) \n
Le mot-clé template : quand un template est à l'intérieur d'un template
\n\nLe cœur du problème
\n\nAvec template, la situation est symétrique. Il est nécessaire lorsque vous accédez à un membre template à l'intérieur d'un template.