pourquoi ne pas avoir besoin de la déclaration anticipée dans la dissortingbution statique via des modèles

Je joue un peu avec le polymorphism statique, j’appelle une fonction qui appelle en interne la “bonne” fonction spécialisée en fonction du type de l’argument initial (en gros, je fais du balisage). Voici le code:

#include  using namespace std; // tags struct tag1{}; struct tag2{}; // the compliant types, all should typedef tag_type struct my_type1 { using tag_type = tag1; }; struct my_type2 { using tag_type = tag2; }; // static dispatch via tagging template  void f(T) { cout << "In void f(T)" << endl; // why can I call f_helper without forward definition?!? f_helper(typename T::tag_type{}); } int main() { my_type1 type1; my_type2 type2; // how does f below knows about f_helper ?!?! // even after instantiation f_helper shouldn't be visible! f(type1); f(type2); } // helper functions void f_helper(tag1) { cout << "f called with my_type1" << endl; } void f_helper(tag2) { cout << "f called with my_type2" << endl; } 

Donc, f(T) est appelé avec un paramètre my_type1 ou my_type2 qui doit en interne tag_type typedef tag_type avec la balise appropriée tag2 / tag2 . En fonction de ce tag_type interne, le “bon” wrapper est alors appelé, et cette décision est bien sûr prise au moment de la compilation. Maintenant, je ne comprends vraiment pas pourquoi ce code fonctionne? Pourquoi n’avons-nous pas besoin de déclarer en avant f_helper ? Les wrappers ont d’abord été définis avant main (et après f ), et j’ai bien compris, cela n’a aucun sens, il n’est pas nécessaire d’envoyer forward declare car le compilateur instancie le modèle uniquement lorsque f(type1); est appelé (dans main() ), avant de ne pas connaître le type T , donc au moment de l’instanciation, le compilateur connaît f_wrapper .

Mais comme vous le voyez, même si je déclare les wrappers APRES main() , le code fonctionne toujours. Pourquoi cela arrive-t-il? Je suppose que la question est un peu étrange, demandant pourquoi un code fonctionne 🙂


MODIFIER

La compilation du code continue même dans gcc5 et gcc HEAD 6.0.0.

f_helper(typename T::tag_type{}) est une expression dépendante du type car T::tag_type est un type dépendant. Cela signifie que f_helper n’a pas besoin d’être visible jusqu’à ce que f soit instancié en raison d’une recherche en deux phases.

EDIT: Je suis à peu près sûr que c’est en fait un comportement indéfini. Si nous regardons 14.6.4.2 [temp.dep.candidate] nous voyons ce passage:

Pour un appel de fonction dépendant d’un paramètre de modèle, les fonctions candidates sont trouvées à l’aide des règles de recherche habituelles (3.4.1, 3.4.2, 3.4.3), sauf que:

– Pour la partie de la recherche utilisant une recherche de nom non qualifiée (3.4.1) ou qualifiée (3.4.3), seules les déclarations de fonction du contexte de définition de modèle sont trouvées.

– Pour la partie de la recherche utilisant les espaces de nom associés (3.4.2), seules les déclarations de fonction trouvées dans le contexte de définition de modèle ou dans le contexte d’instanciation de modèle sont trouvées.

Si le nom de la fonction est un identificateur non qualifié et que l’appel serait mal formé ou trouverait une meilleure correspondance si la recherche dans les espaces de noms associés était considérée comme prenant en compte toutes les déclarations de fonctions avec liaison externe introduites dans ces espaces de noms dans toutes les unités de traduction, ces déclarations trouvées dans la définition de modèle et les contextes d’instanciation de modèle, le programme a alors un comportement indéfini.

Le dernier paragraphe m’indique qu’il s’agit d’un comportement indéfini. L’ function call that depends on a template parameter ici est f_helper(typename T::tag_type{}) . f_helper n’est pas visible lorsque f est instancié, mais ce serait le cas si nous recherchions le nom une fois que toutes les unités de traduction ont été compilées.

Je suis d’accord, le code est mal formé. Je suis surpris que ni g ++ ni clang ++ n’a même un avertissement à ce sujet.

14.6.2 / 1:

Dans une expression de la forme:

  • postfix-expression ( expression-list [ opt ] )

postfix-expression est une expression id , expression id désigne un nom dépendant si l’une des expressions de la liste d’expressions est une expression dépendante du type (14.6.2.2) ou si l’ identificateur non qualifié de l’identificateur id expression est un template-id dans lequel l’un des arguments du template dépend d’un paramètre template. … Ces noms ne sont pas liés et sont recherchés au sharepoint l’instanciation du modèle (14.6.4.1), à la fois dans le contexte de la définition du modèle et dans celui du point d’instanciation.

[ f_helper est une expression postfix et id-expression , et le typename T::tag_type{} dépend du type, donc f_helper est un nom dépendant.]

14.6.4 / 1:

Lors de la résolution de noms dépendants, les noms provenant des sources suivantes sont pris en compte:

  • Déclarations visibles au moment de la définition du modèle.

  • Les déclarations provenant des espaces de noms associés aux types des arguments de la fonction, à la fois du contexte d’instanciation (14.6.4.1) et du contexte de définition.

14.6.4.1/6:

Le contexte d’instanciation d’une expression qui dépend des arguments du modèle correspond à l’ensemble des déclarations avec liaison externe déclarées avant le point d’instanciation de la spécialisation de modèle dans la même unité de traduction.

14.6.4.2/1:

Pour un appel de fonction dépendant d’un paramètre de modèle, les fonctions candidates sont trouvées à l’aide des règles de recherche habituelles (3.4.1, 3.4.2, 3.4.3), sauf que:

  • Pour la partie de la recherche utilisant une recherche de nom non qualifiée (3.4.1) ou qualifiée (3.4.3), seules les déclarations de fonction du contexte de définition de modèle sont trouvées.

  • Pour la partie de la recherche utilisant les espaces de nom associés (3.4.2), seules les déclarations de fonction trouvées dans le contexte de définition de modèle ou dans le contexte d’instanciation de modèle sont trouvées.

L’appel à f_helper(typename T::tag_type{}); dépend du paramètre de modèle T , le nom f_helper n’est pas nécessairement visible avant le point d’instanciation de f (en raison de la recherche du nom en deux phases).

Je crois que le code fonctionne parce que les implémentations sont autorisées à retarder le point d’instanciation des modèles de fonctions jusqu’à la fin de l’unité de traduction, moment auquel les définitions de f_helper sont disponibles.

N3936 §14.6.4.1 / 8 [point temp.]

Une spécialisation pour un modèle de fonction, un modèle de fonction de membre, une fonction de membre ou un membre de données statique d’un modèle de classe peut avoir plusieurs points d’instanciation dans une unité de traduction, et s’ajoutant aux points d’instanciation décrits ci-dessus Si la spécialisation a un point d’instanciation dans l’unité de traduction, la fin de l’unité de traduction est également considérée comme un point d’instanciation . Une spécialisation pour un modèle de classe a au plus un point d’instanciation dans une unité de traduction. Une spécialisation pour n’importe quel modèle peut avoir des points d’instanciation dans plusieurs unités de traduction. Si deux points d’instanciation différents donnent à une spécialisation de gabarit des significations différentes selon la règle de définition unique (3.2), le programme est mal formé, aucun diagnostic n’est requirejs.