2016-05-04 1 views
6

Je viens de croiser un comportement intéressant avec recherche dépendante argument, que je ne comprends pas bien:recherche dépendant de l'argument se comportant de façon inattendue sur les types crénelage d'un autre espace de noms

#include <iostream> 

namespace a { 
struct Foo { 
    Foo(int v1, int v2) : v1(v1), v2(v2) { } 
    int v1,v2; 
}; 
} 

namespace b { 
template <typename T> 
struct Baz : T { 
    using T::T; 
}; 
} 

namespace c { 
using Foo = ::b::Baz< ::a::Foo>; 

// (1) NOT FOUND BY ADL 
// std::ostream& operator << (std::ostream& os, const Foo& foo) 
// { 
// return os << foo.v1 << "," << foo.v2; 
// } 
} 

namespace b { 

// (2) FOUND BY ADL 
std::ostream& operator << (std::ostream& os, const ::c::Foo& foo) 
{ 
    return os << foo.v1 << "," << foo.v2; 
} 

} 

int main() 
{ 
    c::Foo foo(1,2); 
    // Variant (1): ADL fails: should it not find 
    // c::operator<<(std::ostream&, const Foo&) ? 
    // Variant (2) compiles 
    std::cout << "foo: " << foo << std::endl; 
} 

Je reçois que c::Foo est en fait b::Baz<...>, il est donc logique que ADL trouve l'opérateur lorsque je le définis à l'intérieur namespace b. Mais il semble défier l'intuition que la définition de l'opérateur à l'intérieur namespace c ne fonctionne pas, puisque c::Foodevrait (IMHO) permettre au compilateur d'effectuer ADL à l'intérieur de namespace c ainsi.

Pourquoi cela n'est-il pas le cas? Quelle est la logique derrière cela?

+0

@mindriot: «Mais cela semble être un détail de mise en œuvre * » Non, ça ne l'est pas. * Nulle part * en C++ vous êtes autorisé à utiliser un alias de type d'une manière qui se comporterait d'une manière différente que si vous utilisiez le type réel. Ce n'est pas un "détail d'implémentation"; c'est le comportement requis pour les alias de type. –

+0

@NicolBolas Je suis d'accord, mais ce n'est pas ce que je voulais dire. Peut-être que je devrais reformuler: Cette _explanation_ particulière ("imagine la référence à réécrire") donne l'impression que c'est juste un détail d'implémentation. La raison que Columbo a donnée dans sa réponse a plus de sens. – mindriot

Répondre

4

[basic.lookup.argdep]/2:

noms typedef et en utilisant déclaration s utilisés pour spécifier les types ne le font pas contribuent à cet ensemble.

Ni les noms de typedef ni les déclarations d'utilisation ne sont affiliés au type qu'ils désignent. Si elles étaient (ce qui serait en fait contre-intuitif, OMI), les choses se casseraient très facilement; juste parce que je typedef une classe, tous les appels envisagent maintenant des fonctions supplémentaires à proximité du typedef, qui est pratiquement jamais désiré:

#include <string> 

namespace A { 
    using str = std::string; 
    int stoi(str); // This will be a candidate in e.g. stoi(std::string{"0.4"}), 
        // leading to potentially different behavior or ambiguity 

namespace B { 
    int stoi(std::string); // This is no candidate, because...? 
} 
+0

Merci, c'est ce que j'attendais: ils sont explicitement laissés de côté. Avez-vous une idée de la raison d'être? Par exemple, est-ce que quelque chose se casserait, ou deviendrait trop compliqué à mettre en œuvre, si cette phrase particulière avait été omise? – mindriot

+0

@mindriot Voir éditer. – Columbo

+0

Merci, cela a du sens! – mindriot