14

The following very simple code won't compile

#include <vector>
#include <string>


namespace Foobar {
    struct Test {
        std::string f;
        std::uint16_t uuid;
    };
}

bool operator==(const Foobar::Test& lhs, const Foobar::Test& rhs){
    return lhs.f == rhs.f && lhs.uuid == rhs.uuid;
}


int main(){

    std::vector<Foobar::Test> a;
    std::vector<Foobar::Test> b;

    if(a==b){

    }

    return 0;
}

https://godbolt.org/g/zn6UgJ

Won't compile in any of the compilers I have.

While the following

#include <vector>
#include <string>


namespace Foobar {
    struct Test {
        std::string f;
        std::uint16_t uuid;
    };

    bool operator==(const Foobar::Test& lhs, const Foobar::Test& rhs){
        return lhs.f == rhs.f && lhs.uuid == rhs.uuid;
    }
}



int main(){

    std::vector<Foobar::Test> a;
    std::vector<Foobar::Test> b;

    if(a==b){

    }

    return 0;
}

https://godbolt.org/g/o4pc1b

Compiles just fine, which makes me think std::vector<T> comparison operator looks in the namespace of T, why won't it consider the global namespace?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
arynaq
  • 6,710
  • 9
  • 44
  • 74
  • @PasserBy that doesn't answer the question. Koenig lookup **adds** namespaces to lookup, but the question is why function in global namespace is not considered. – SergeyA Jul 20 '18 at 17:52
  • @SergeyA Hmm.. fair point. I guess it's too much to ask for to intuit the rest of the problem. But then you can _kinda_ get the feel of why this happens, without the exact details. – Passer By Jul 20 '18 at 17:54
  • I nominated the question for reopening, as duplicate doesn't answer the question on 'why function in global namespace is not considered' – SergeyA Jul 20 '18 at 17:55
  • I have seen this exact same behavior in Qt's QVector as well. For some reason the compiler does not like when something is in a vector and the vector item's comparison operator is defined outside of the class. I don't think the namespace matters that much. It's whether you declare it inside or outside of the class. This could be something that std::vector requires. – santahopar Jul 20 '18 at 18:12
  • @SergeyA What dup was nominated? – curiousguy Jul 23 '18 at 09:16
  • @curiousguy don't remember the full link, but it was a question on what is ADL lookup. – SergeyA Jul 25 '18 at 14:44

1 Answers1

10

Ordinary unqualified name lookup starts looking in the context where the name is used, and walks up the chain of enclosing scopes. It stops in the most nested scope that contains the matching name. This is true even if the name thus found is later determined to be unsuitable (e.g. the function overload is non-viable for a given call; or the member function is inaccessible).

Here, the context of the lookup is std::operator==(vector, vector), so it starts looking in namespace std. There are plenty of overloads of operator== in namespace std, so the ordinary lookup stops there and never reaches the global namespace.

In the second example, the overload is found by argument-dependent lookup. This lookup is performed specifically for function names in function calls, in addition to unqualified lookup, and looks for names in scopes associated with the types of the call's arguments. In the example, namespace Foobar is associated with Foobar::Test, and so argument-dependent lookup searches that namespace and finds Foobar::operator==.

For this reason, free functions that are logically part of the class' public interface - e.g. overloaded operators - should generally be defined in the same namespace as the class itself, to give argument-dependent lookup a chance to work. std::operator==(vector, vector) is a good example of that - a==b in your example works by way of argument-dependent lookup.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • I don't get the part "the context of the lookup is `std::operator==(vector, vector)` [...] it starts looking in namespace `std` [...] so the ordinary lookup stops there". Isn't the lookup in question the one comparing vector elements of type `T`? Why should the lookup for `T == T` start in `std` and stop there because it finds "plenty of overloads"? Do you mind helping me out here? – lubgr Jul 20 '18 at 19:03
  • 1
    @lubgr Consider `int i; void foo() { void* i; i = 42; }`. The usual lookup happens from the definition of the equality operator for `std::vector`, ie from `std`. – Passer By Jul 20 '18 at 19:14
  • 1
    @PasserBy Thanks for the explanation, that makes sense to me. Why does it stop in `std` though when it doesn't find anything matching? Wouldn't it be straightforward to continue searching in the outer scope? – lubgr Jul 20 '18 at 19:40
  • @lubgr read the 1st para of Igor Tandetnik answer carefully, it stops on the 1st match of name __name__ alone. – Richard Critten Jul 20 '18 at 20:23
  • 1
    So you are saying that `::operator==(const Foobar::Test& lhs, const Foobar::Test& rhs)` is hidden by `std::operator==(const vector& lhs, const vector& rhs)`, right? – curiousguy Jul 23 '18 at 09:20
  • @curiousguy Yes. Not just `std::operator==(const vector& lhs, const vector& rhs)` specifically - there's a metric boatload of `operator==` overloads in namespace `std`, each of which hides `::operator==` when the lookup starts from inside `std`. – Igor Tandetnik Jul 23 '18 at 13:16
  • @IgorTandetnik Whether boatload is declared at that point where the function body is compiled depends on the amount of `#include`, at least we know that `std::operator==(const vector& lhs, const vector& rhs)` must be declared in `#include `. (Which BTW is dirty as the semantic of a some other function body could potentially change depending on whether another header is included or not.) – curiousguy Jul 23 '18 at 15:14