I'm trying to test my project in the latest Visual Studio and Clang versions. One of the errors that pops up is related to an ambiguous operator (with reversed parameter order)
. This does not seem to pop up in C++17.
For example: (https://godbolt.org/z/Gazbbo)
struct A {
bool operator==(const A& other) const { return false; }
};
struct B : private A {
B(const A&);
bool operator==(const B& other) const { return false; }
};
bool check(A a, B b) {
return b == a;
}
I'm unsure why this would be an issue. It seems to me that the only viable function here is bool operator==(const B& other) const
as A
can be implicitly converted to B
but not the reverse. Indeed, if I mark B(const A&)
with explicit
I instead get an error that B
can not be converted to the private base of A
.
I'm trying to understand what I can do to avoid this, aside from using explicit
or using B(a)
. Imagine A
and B
were library code, how do I support C++20 without breaking my interface in lower versions?
In C++17, yes, the only viable candidate was b.operator==(B(a))
.
But in C++20, comparison operators have more functionality. Equality can now consider reversed and rewritten candidates as well. So when consider the expression b == a
we also consider the expression a == b
. As a result, we have two candidates:
bool B::operator==(B const&);
bool A::operator==(A const&); // reversed
The B
member function is an exact match on the left-hand side but requires converting the 2nd argument. The A
member function is an exact match on the right-hand side but requires converting the 1st argument. Neither candidate is better than the other, so the result becomes ambiguous.
As for how to fix it. This is kind of a strange scenario (B
both inherits from A
and is constructible from A
?). if you drop the inheritance, you remove the A
member candidate. If you remove the B(A const&)
constructor, then you get an access violation since the only candidate is the one comparing A
's which requires converting b
to its A
(which kind of suggests that this is questionable).
Alternatively, you could add, to B
, a direct comparison to A
to define what that actually means. Since the issue here is that there are two choices, the compiler doesn't know which one is best, so just provide a better one:
struct B : private A {
B(const A&);
bool operator==(B const&) const;
bool operator==(A const&) const; // <== add this one
};
Now this new one is an exact match in both arguments and is the strictly superior candidate.