2016-07-12 1 views
3

Je souhaiterais que l'on mette un peu de lumière sur une situation embarrassante impliquant l'ADL, les espaces de noms et la surcharge de l'opérateur.Surcharge de l'opérateur, résolution des noms et espaces de noms

Soit Foo une bibliothèque qui définit une classe (Deriv) dans son propre espace de noms, avec un operator * qui retourne une autre basé sur un modèle de classe.

namespace Foo { 
    class Deriv {}; 
    class Another {}; 

    template <typename T> 
    Another operator* (T x, const Deriv& d) { return Another();} 
} 

Maintenant j'utilise la classe de Foo dans ma propre bibliothèque Bar, qui définit une autre operator *, cette fois seulement pour float.

namespace Bar { 
    typedef Foo::Deriv MyDeriv; 
    MyDeriv operator* (float x, const MyDeriv& d) { return MyDeriv();} 
} 

j'observe une différence de comportement du compilateur selon que l'on est à l'intérieur namespace Bar ou non.

Cette fonction (Bar::f1) compile, en utilisant la deuxième version du operator *:

namespace Bar { 
    void f1() { 
     Bar::MyDeriv a; 
     Bar::MyDeriv b = 3.f * a; 
    } 
} 

tandis que la même fonction en dehors de l'espace de noms Bar (f2()) ne parvient pas à compiler, parce que les tentatives du compilateur que d'utiliser Foo::operator* et ne peut pas devinez qu'il doit utiliser Bar::operator*.

void f2() { 
    Bar::MyDeriv a; 
    Bar::MyDeriv b = 3.f * a; // Error : cannot convert Foo:Another to Bar::Myderiv 
} 

Vous pouvez voir le code en direct ici: http://ideone.com/pkPeOY

Maintenant, si Foo::operator* n'a pas été basé sur des modèles et définis comme Foo::operator*(float, const Deriv& d); puis les deux fonctions ne parviennent pas à compiler avec la même erreur (surcharge de l'opérateur ambigu), comme peut être vu ici: http://ideone.com/wi1EWS

Alors, face à cette situation, voici ce qui me laisse perplexe

  • Dans le cas basé sur un modèle, lors de la compilation f2, le compilateur envisage d'utiliser Foo::operator* mais pas Bar::operator*, tandis que dans le non- basé sur un modèle, il considère utilisant à la fois (et refuse d'aller plus loin en raison de l'ambiguïté). Qu'est-ce qui fait que le compilateur se comporte différemment?

  • Un utilisateur de ma bibliothèque Bar sera en dehors de l'espace de noms Bar::, mais je veux Bar::operator* à utiliser, et non Foo::operator*. J'ai envisagé d'appeler explicitement Bar::operator*(3.f,a), ce qui est moche, ou d'insérer mon propre opérateur dans l'espace de noms global, que je suppose être BadThing. Y at-il une option qui me manque, ou est-ce que je fais quelque chose de mal?

Répondre

3

Dans le cas basé sur un modèle, lors de la compilation f2, le compilateur considère en utilisant Foo::operator* mais pas Bar::operator*, alors que dans le cas de non-basé sur un modèle, il envisage d'utiliser à la fois (et refuse d'aller plus loin en raison de l'ambiguïté). Qu'est-ce qui fait que le compilateur se comporte différemment?

Dans les deux cas, le compilateur envisage d'utiliser les deux, mais dans le cas d'un operator* basé sur un modèle, l'appel est pas ambigu car il est une fonction non-basé sur un modèle qui types de paramètres correspond parfaitement les arguments (essayez de remplacer 3.f avec 3. et vous verrez que la version sur gabarit est trouvée). En règle générale:

template <typename T> 
void g (T) { } 

void g (float) { } 

g(0.f); // Ok, the overload for float is preferred over the templated version 

Un utilisateur de ma bibliothèque Bar sera en dehors de l'espace de noms Bar::, mais je veux Bar::operator* à utiliser, et non Foo :: operator *. J'ai envisagé d'appeler explicitement Bar::operator*(3.f,a), ce qui est moche, ou d'insérer mon propre opérateur dans l'espace de noms global, ce que je pense être une mauvaise chose. Y a-t-il une option qui me manque ou est-ce que je fais quelque chose de mal?

Malheureusement, ADL ne trouvez pas votre surcharge puisque les seuls paramètres de operator* sont float et MyDeriv qui sont définies dans l'espace de noms Foo. Une façon possible serait d'hériter de Foo::Deriv:

namespace Bar { 
    struct MyDeriv: public Foo::Deriv {}; 
    MyDeriv operator* (float x, const MyDeriv& d) { return MyDeriv();} 
} 

Un autre est de déclarer votre surcharge pour operator* l'intérieur de l'espace de noms Foo:

namespace Bar { 
    typedef Foo::Deriv MyDeriv; 
} 

namespace Foo { 
    Bar::MyDeriv operator* (float x, const Bar::MyDeriv& d) { return Bar::MyDeriv(); } 
} 
+0

@GuyGreer S'il n'y a rien virtuel dans 'Foo :: Deriv ', Je pense qu'il n'y aura aucun inconvénient en termes de taille (et un bon compilateur devrait optimiser tout, je suppose ...). – Holt

+0

Hmm, pour une raison quelconque, je pensais que la classe dérivée devait avoir sa propre adresse ... Je pense que je devrais garder pour moi aujourd'hui, je ne suis pas sur mon jeu. – SirGuy

+0

Yeh désolé de la confusion. Au début, je pensais que c'était parce que Deriv faisait partie d'un CRTP (donc une classe dérivée) mais c'était sans rapport avec le problème, donc j'ai supprimé l'héritage (mais j'ai gardé le nom, ce qui peut être déroutant). – Louen