4

The following code doesn't compile, as the comparison operator is not found.

#include <vector>
#include <iostream>
#include <string>

namespace Cool {
    struct Person {
        std::string name;
    };
}

bool operator==(const Cool::Person&  p1, const Cool::Person& p2) {
    return p1.name == p2.name;
}

int main(int, char *[])
{
    std::vector<Cool::Person> a{ {"test"} };
    std::vector<Cool::Person> b{ {"test"} };
    bool ok = a == b;
    std::cout << ok << std::endl;
}

After some experiments I found out, that the following perfectly compiles:

#include <vector>
#include <iostream>
#include <string>

namespace Cool {
    struct Person {
        std::string name;
    };

    bool operator==(const Person&  p1, const Person& p2) {
        return p1.name == p2.name;
    }
}

int main(int, char *[])
{
    std::vector<Cool::Person> a{ {"test"} };
    std::vector<Cool::Person> b{ {"test"} };
    bool ok = a == b;
    std::cout << ok << std::endl;
}

Can someone explain the rationale behind this behavior?

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
Aleph0
  • 5,816
  • 4
  • 29
  • 80
  • 5
    Someone who happens to know the exact clause that governs overload resolution and/or lookups would probably cite it, very soon, but it simply boils down: "thems are the rules". – Sam Varshavchik Jun 04 '19 at 12:35
  • 1
    Possible duplicate of [What is "Argument-Dependent Lookup" (aka ADL, or "Koenig Lookup")?](https://stackoverflow.com/questions/8111677/what-is-argument-dependent-lookup-aka-adl-or-koenig-lookup) – Aykhan Hagverdili Jun 04 '19 at 12:44
  • If you define `a` and `b` as `Cool::Person` and not as `std::vector<...>`, it works. Looks like compiler search first in `std`, then in `Cool` and then stops, without trying then to search in current namespace – Damien Jun 04 '19 at 13:03

1 Answers1

7

This is called ADL, or Argument dependent lookup.

For operators, the compiler will not only search in the current namespace for a suitable function, but in the namespaces of the arguments too.

For example:

int main() {
    int arr[3] = {};
    std::vector<int> vec(3);

    auto b_vec = begin(vec); // std::begin
    auto b_arr = begin(arr); // Error!
}

When calling begin with vec, it will search in the std namespace since std::vector is in that namespace. For a raw array, the function is not found since it has no namespace associated to that type.

One way to turn off ADL is to simply qualify the function:

// calls the std:: one, not the boost:: one
std::begin(some_boost_container);

This is also how friend function works:

struct test {
    friend void find_me(int) {}
};

find_me(3); // uh? no matching function?

Even though the function is perfectly fine, it cannot be found.

It need the name of the class inside it's argument so ADL kicks in and find it in the class scope:

struct test {
    friend void find_me(test const&) {}
};

find_me(test{}); // works!

So... for operators? why does it work?

because calling a user defined operator is roughly equivalent to that:

// arg1 == arg2;
operator==(arg1, arg2);

Since the function name is not qualified, ADL kicks in. Then find the operator in the right namespace and can also find friend functions.

This both allow a nice syntax and also allow generic function to call function inside your namespace.


So why the vector cannot find the one in the global namespace?

This is because how ADL works. Inside a namespace with a function with a name in it, ADL will only search inside the namespace of the argument. But if one cannot be found, it will fallback to normal lookup.

namespace Cool {
    struct Person {
        std::string name;
    };
}

bool cant_you_find_me(Cool::Person const& p);

namespace test {
    void cant_you_find_me();

    template<typename T>
    void test_template(T const& p) {
        cant_you_find_me(p); // ADL?
    }
}

In this example, ADL will search for a function cant_you_find_me, but if one cannot be found through ADL, the global namespace will not be considered since normal lookup will find the closest one instead: the one taking no arguments.

This is what happens with the std namespace. It has many operator== defined in it. If ADL does not find a suitable one, the global namespace will not be considered, but the ones in the std instead.

Aykhan Hagverdili
  • 28,141
  • 6
  • 41
  • 93
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141