I'm having trouble with the C++ standard library. The following example does not compile: (note this is cut down to make a minimal example so does not 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;
}
The error is "binary '==': no operator found which takes a left-hand operand of type 'otherns::Property' (or there is no acceptable conversion)" which occurs 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 does work:
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 suppose this is something to do with ADL/Koenig lookup but I don't really understand why my operator==
is not found. what's the best solution if I want to use the first, simpler form of the find
function?
In reality the otherns
comes from a header for a 3rd party library so I can't put my operator into that header.
The rules are quite complicated, and I myself don't fully grasp them, but let's see if we can make head or tails of them (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 isn't found for a simple reason: operator==
is not declared before its use. Nothing to to with ADL yet. So far so good. We understand that. Let's 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{});
}
Does this work? Yes, it does, it compiles and calls our operator==
. Is this the correct solution? 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 global namespace, even if (1) would be a better fit. This is called name hiding and - again - doesn't involve ADL in any way.
Even 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{});
}
Would compile and silently calls (2)
instead of our operator (1)
.
For a real world context think of namespace ns
as namespace std
and
any operator declared inside std
. And you've got the situation in your post.
The correct solution is:
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{});
}
What happens here is that ADL kicks in and brings (1)
from nx
and now (1)
is considered alongside (2)
. But (1)
is more specialized than (2)
and so (1)
is correctly selected.
If you don't have control of namespace nx
and can't add the operator there, then what I can advise is to use callables instead of relying on operators. E.g instead of std::find
use std::find_if
with your own predicate (lambda) where you control exactly which method/operator to call. And when I say "exactly" I mean exactly: i.e. ::operator==(x1, x2)
(or whatever namespace you declared them) instead of x1 == x2
.
You can read more on this great article by Herb Sutter Namespaces & Interface Principle
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With