2016-04-19 2 views
8

Dans l'exemple de code C++ suivant, GCC 6 et Clang 3.8 en désaccord sur ce que le comportement correct est:fonctions amies modèle et les espaces de noms de l'objet modèle

Cet exemple artificiel « fonctionne » - comme dans la retourne fonction test()o.p dans GCC. Dans clang, il appelle la fonction get<int, int, float, double> (non défini):

template<typename ...Args> 
class obj { 
bool p = false; 

template<typename T, typename... Args2> 
friend T get(const obj<Args2...> &o) { return o.p; } 
}; 

template<typename T, typename... Args> 
T get(const obj<Args...> &o); 


bool test(const obj<int, float, double> &a) { 
return get<int>(a); 
} 

Mettre le même code dans un espace de noms provoque GCC de faire la même chose clang fait.

namespace ns { 

template<typename ...Args> 
class obj { 
bool p = false; 

template<typename T, typename... Args2> 
friend T get(const obj<Args2...> &o) { return o.p; } 
}; 

template<typename T, typename... Args> 
T get(const obj<Args...> &o); 

} 

bool test(const ns::obj<int, float, double> &a) { 
return ns::get<int>(a); 
} 

https://godbolt.org/g/sWrXQO et https://godbolt.org/g/9tIXwe

compilateur est « correct » et est là en général un moyen de définir un membre d'ami en ligne de fonction de modèle sans avoir à déclarer et définir séparément. Autrement dit, des choses comme:

struct Foo { 
friend bool bar() { return true; } // declares *and* defines a free function bar 
template<typename T> T bar2() { return true; } // doesn't work! 
}; 
+0

Pourquoi déclarer 'template T get (const obj &o); classe' en dehors – Jarod42

+0

Parce que cela ne fonctionne pas si vous ne ... essayez: https://godbolt.org/g/peSscU –

+0

Oui, 'T' n'est pas déductible, donc vous devez fournir' ', donc ADL ne fonctionne pas (car il n'y a pas de modèle' get' dans la portée globale):/Vous pouvez déclarer un modèle fictif obtenir (avec une correspondance incorrecte) pour réactiver l'ADL: [Demo] (http://coliru.stacked-crooked.com/a/e40aaf90de712943) – Jarod42

Répondre

5

Il y a deux questions non résolues concernant friend modèles de fonction définis dans les modèles de classe: 1545 et 2174. La première questionne la mesure dans laquelle elle est valide et la dernière concerne les violations d'odr qui peuvent survenir en fonction des instanciations réelles de ces modèles de fonction. Je ne suis pas sûr quel compilateur a raison (ayant déjà cru que les deux étaient faux), mais il peut simplement être sous- ou mal spécifié dans la norme quel est le comportement correct dans cette situation.

Le code devrait compiler idéalement (en attendant la résolution des problèmes):

template<typename ...Args> 
class obj { 
bool p = false; 

template<typename T, typename... Args2> 
friend T get(const obj<Args2...> &o) { return o.p; } 
}; 

template<typename T, typename... Args> 
T get(const obj<Args...> &o); 

La déclaration friend premier déclare get, donc cela crée un nouveau membre de l'espace de noms englobante la plus profonde: ::get. La déclaration externe vient de redéclarer la même fonction, donc il n'y en a vraiment qu'une ::get. De [temp.over.link]:

Deux expressions impliquant des paramètres du modèle sont considérés comme équivalents si deux définitions de fonctions contenant les expressions satisferait la règle d'une définition (3.2), à l'exception que les jetons utilisés pour nommer le Les paramètres de modèle peuvent différer tant qu'un jeton utilisé pour nommer un paramètre de modèle dans une expression est remplacé par un autre jeton qui nomme le même paramètre de modèle dans l'autre expression. Pour déterminer si deux noms dépendants (14.6.2) sont équivalents, seul le nom lui-même est considéré, pas le résultat de la recherche de nom dans le contexte du modèle.

En utilisant différents noms de paramètres du modèle (Args... vs Args2...) est très bien - cette deuxième déclaration du modèle de fonction ::get est valide et permet pour la recherche pour le trouver.

Cela nous amène à:

bool test(const obj<int, float, double> &a) { 
#ifdef UNQUAL 
    return get<int>(a);  // unqualified 
#else 
    return ::get<int>(a); // qualified 
#endif 
} 

Ces deux devraient travail - puisque nous redéclarée la fonction d'être portée d'espace de noms, nous n'avons même pas à se soucier de l'ADL.Les deux appels devraient trouver ::get<int>(obj<int, float, double>), qui est un friend, et donc le code devrait compiler et lier. gcc permet l'appel non qualifié mais pas l'appel qualifié, clang n'autorise ni l'un ni l'autre.

Nous pourrions contourner les problèmes CWG entièrement simplement pas définir le modèle de fonction à l'intérieur de la classe:

template<typename ...Args> 
class obj { 
bool p = false; 

template<typename T, typename... Args2> 
friend T get(const obj<Args2...> &o); 
}; 

template<typename T, typename... Args> 
T get(const obj<Args...> &o) { return o.p; } 

Avec cette formulation, les deux compiles permettent à la fois appel qualifiés et non qualifiés de get.


Si nous réécrivons le code tel que obj est une classe et non un modèle de classe, mais toutes choses étant égales:

class obj { 
    bool p = false; 

    template <class T> 
    friend T get(const obj& o) { return o.p; } 
}; 

template <class T> T get(const obj&); 

bool test(const obj& a) { 
#ifdef UNQUAL 
    return get<int>(a); 
#else 
    return ::get<int>(a); 
#endif 
} 

Les deux compilateurs permettent aux deux invocations. Mais il n'y a aucune différence que je connais dans les règles entre obj étant une classe normale et un modèle de classe.

+0

Merci pour la réponse complète. J'apprécie la solution de contournement (de définir le get à l'extérieur) - et c'est ce que je fais déjà dans mon code. Cependant la disparité entre la fonction non-templatée pouvant être définie en ligne (et couramment faite avec par exemple 'friend operator ''), et pourtant dès que la fonction est elle-même modélisée, devoir séparer sa déclaration et sa définition ... est tout simplement déroutant. Mais ... c'est C++ :) –

+2

@ MattG L'exigence de modèle externe peut être mieux expliquée dans [ce commentaire] (http://stackoverflow.com/questions/2953684/why-doesnt-adl-find-function-templates#comment29830195_2953783). En outre, je ne suis pas sûr à 100% que mon raisonnement est juste au sujet des compilateurs. En espérant que quelqu'un d'autre intervienne ici à un moment donné. – Barry

+1

CWG2174 et CWG1545 pourraient être pertinents. –