Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparing std::vector using own class in namespace does not compile

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?

like image 777
Aleph0 Avatar asked Mar 03 '23 21:03

Aleph0


1 Answers

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.

like image 81
Guillaume Racicot Avatar answered Mar 06 '23 21:03

Guillaume Racicot