How can I specify an overloaded statement in a different namespace?

I am having problems with the standard C ++ library. The following example does not compile: (note that this is a shortcut to make a minimal example, so it doesn't make much sense as it is)

#include <algorithm> #include <string> #include <vector> namespace otherns { class Property { public: const std::string &getName() const { return m_name; } private: std::string m_name; }; } bool operator==(const otherns::Property &a, const otherns::Property &b) { return a.getName() == b.getName(); } /* Merge, second takes priority */ std::vector<otherns::Property> merge_props(const std::vector<otherns::Property> &xs, const std::vector<otherns::Property> &ys) { std::vector<otherns::Property> ans = ys; for (const auto &x : xs) { if (std::find(ans.begin(), ans.end(), x) == ans.end()) { ans.push_back(x); } } return ans; } 

Error "binary" == ': the operator was not found, which accepts the left operand of type "otherns :: Property" (or there is no acceptable conversion) ", which happens somewhere in the implementation of std::find , This is with MSVC, but I also tried with clang and gcc with a similar result.

The following code works:

 std::vector<otherns::Property> merge_props(const std::vector<otherns::Property> &xs, const std::vector<otherns::Property> &ys) { std::vector<otherns::Property> ans = ys; for (const auto &x : xs) { if (std::find_if(ans.begin(), ans.end(), [&x](const otherns::Property &y) { return x == y; }) == ans.end()) { ans.push_back(x); } } return ans; } 

I believe this is related to ADL / Koenig search, but I don't understand why my operator== not found. what's the best solution if I want to use the first, simpler kind of find function?

In reality, otherns comes from the header for a third-party library, so I cannot put my statement in that header.

+5
source share
3 answers

The rules are quite complicated, and I myself do not fully understand them, but let's see if we can make them head or tails (I think we can):

 namespace nx { struct X {}; } namespace ns { auto foo(nx::X x1, nx::X x2) { return x1 == x2; } // error: no match for 'operator==' (operand types are 'nx::X' and 'nx::X') } auto operator==(nx::X, nx::X) { return true; } auto global_foo() { return ns::foo(nx::X{}, nx::X{}); } 

This was not found for a simple reason: operator== not declared before using it. With ADL, nothing yet. So far, so good. We understand that. Let fix it:

 namespace nx { struct X {}; } auto operator==(nx::X, nx::X) { return true; } namespace ns { auto foo(nx::X x1, nx::X x2) { return x1 == x2; } } auto global_foo() { return ns::foo(nx::X{}, nx::X{}); } 

It works? Yes, it does, it compiles and calls our operator== . This is the right decision? No! . Because if we add this:

 namespace nx { struct X {}; } auto operator==(nx::X, nx::X) { return true; } // (1) namespace ns { template <class T> auto operator==(T, int) { return false; } // (2) auto foo(nx::X x1, nx::X x2) { return x1 == x2; } // error: no match for 'operator==' (operand types are 'nx::X' and 'nx::X') } auto global_foo() { return ns::foo(nx::X{}, nx::X{}); } 

Then (2) in ns hides (1) in the global namespace, even if (1) matches better. This is called name hiding and - again - is not related to ADL in any way.

Worse:

 namespace nx { struct X {}; } auto operator==(nx::X, nx::X) { return true; } // (1) namespace ns { template <class T> auto operator==(T, T) { return false; } // (2) auto foo(nx::X x1, nx::X x2) { return x1 == x2; } // calls (2) } auto global_foo() { return ns::foo(nx::X{}, nx::X{}); } 

Compiled and silent (2) instead of our operator (1) .

For real-world context, think of namespace ns as the std and any statement declared inside std . And you have a situation in your post.

Correct solution:

 namespace nx { struct X {}; auto operator==(nx::X, nx::X) { return true; } // (1) } namespace ns { template <class T> auto operator==(T, T) { return false; } // (2) auto foo(nx::X x1, nx::X x2) { return x1 == x2; } // calls (1) } auto global_foo() { return ns::foo(nx::X{}, nx::X{}); } 

Here, what happens is that ADL fires and brings (1) from nx , and now (1) considered next to (2) . But (1) more specialized than (2) , and therefore (1) correctly selected.

If you do not have control over namespace nx and you cannot add an operator there, then I can advise using instead of relying on operators. Instead of std::find use std::find_if with your own predicate (lambda), where you determine exactly which method / operator to call. And when I say "exactly," I mean exactly : ie ::operator==(x1, x2) (or any other namespace you declared) instead of x1 == x2 .


For more on this great article, you can read Herb Sutter Namespaces and the Principle of the Interface

+1
source

Just declare operator== inside the otherns namespace (the search will find it in the namespace area)

 namespace otherns { bool operator==(const otherns::Property &a, const otherns::Property &b) { return a.getName() == b.getName(); } } 

Working code

You can do this in a separate third-party library header.

+1
source

You defined operator== in the global namespace (you may have mistakenly entered the wrong representation). It will not be found there, depending on the arguments.

The statement must be declared in the same namespace as (one of) its arguments:

 namespace otherns { class Property { public: const std::string &getName() const { return m_name; } private: std::string m_name; }; bool operator==(const otherns::Property &a, const otherns::Property &b) { return a.getName() == b.getName(); } } 

This small change makes your example easy to compile.

0
source

Source: https://habr.com/ru/post/1262935/


All Articles