Ambiguity error accessing equality comparison operator in G++ - c++

In case of multiple inheritance if all parent classes have their own equality comparison operator ==, and the child class has a (friend) function operator ==, as in the example:
struct A {
bool operator ==(const A&) const = default;
};
struct B {
bool operator ==(const B&) const = default;
};
struct C : A, B {};
constexpr bool operator ==(const C&, const C&) { return true; }
static_assert( C{} == C{} );
GCC prints the following error about ambiguity in an attempt to compare child objects:
error: request for member 'operator==' is ambiguous
13 | static_assert( C{} == C{} );
note: candidates are: 'bool B::operator==(const B&) const'
note: 'bool A::operator==(const A&) const'
Demo: https://gcc.godbolt.org/z/W3qer376d
This error looks surprising, since operator ==(const C&, const C&) shall be preferred as the most suitable to the actual arguments. Is it simply a defect in GCC?

Indeed the global operator operator==(const C&, const C&) has priority. However the compiler "want to know" all the possibilities in order to decide: In other words: as soon as you try to use the operator== the compiler requires you to have no ambiguities, even if the required alternative is not between the ambiguous one.
struct C : A, B { using A::operator==; };
This resolves the ambiguity and allows you to use the global operator==
https://onlinegdb.com/nxMjPN4NC
#include <iostream>
struct A {
bool operator ==(const A&) const { std::cout << "A" << std::endl; return true; };
};
struct B {
bool operator ==(const B&) const { std::cout << "B" << std::endl; return true; };
};
struct C : A, B
{
//using A::operator==; // Uncomment this to make it work.
};
bool operator ==(const C&, const C&) { std::cout << "C" << std::endl; return true; }
int main()
{
C c1, c2;
c1==c2;
return 0;
}
Result (when resolved):
C

Related

Adding unrelated operator == renderers spaceship operator(<=>) non-functional

In this code example, it seems that adding unreleated operator== (on a different type) renders the shaceship opreator not functional. This is tested in VS2019. Is it a compiler bug, or some weird behaviour?
class A
{
public:
int x;
};
class B
{
public:
constexpr std::strong_ordering operator<=>(const B& other) const = default;
constexpr bool operator==(const A& other) const { return this->x == other.x; } // remove this so it can compile
int x;
};
class C
{
public:
constexpr operator bool() const { return b1 == b2; }
B b1, b2;
};
The error I get is in the usage of the operator in the class B is:
binary '==': no operator found which takes a left-hand operand of type 'const B' (or there is no acceptable conversion)
Referring to cppreference's page on how defaulted comparison operators work, let's examine what's happening.
For the first case, with no user-declared operator==:
class B
{
public:
constexpr std::strong_ordering operator<=>(const B& other) const = default;
int x;
};
The following applies:
If operator<=> is defaulted and operator== is not declared at all, then operator== is implicitly defaulted.
The default signature for operator== is bool operator==(const B& other) const. All well and good.
In your second version, you do declare an operator==. This means you do not get the defaulted operator== as a side-effect of defaulting operator<=>.
class B
{
public:
constexpr std::strong_ordering operator<=>(const B& other) const = default;
constexpr bool operator==(const A& other) const { return this->x == other.x; }
// No additional `operator==` will be generated aside from the above
int x;
};
The problem is that the operator== you declared takes a const A& as the right hand side, instead of a const B&. So you cannot use this operator== to perform a check of the form b1 == b2.
If you want to have both the operator==(const A&) const overload and the default operator==(const B&) const overload, you can explicitly request that the latter be defaulted, like so:
class B
{
public:
constexpr std::strong_ordering operator<=>(const B& other) const = default;
constexpr bool operator==(const B& other) const = default;
// Explicitly request default operator== as well as default operator<=>
constexpr bool operator==(const A& other) const { return this->x == other.x; }
int x;
};

Why can I invoke == with a defaulted <=> but not a user-provided one?

#include <compare>
struct A
{
int n;
auto operator <=>(const A&) const noexcept = default;
};
struct B
{
int n;
auto operator <=>(const B& rhs) const noexcept
{
return n <=> rhs.n;
}
};
int main()
{
A{} == A{}; // ok
B{} == B{}; // error: invalid operands to binary expression
}
compiled with clang-10 as clang -std=c++20 -stdlib=libc++ main.cpp
Why does A{} == A{} work but not B{} == B{}?
In the original design of the spaceship operator, == is allowed to call <=>, but this is later disallowed due to efficiency concerns (<=> is generally an inefficient way to implement ==). operator<=>() = default is still defined to implicitly define operator==, which correctly calls == instead of <=> on members, for convenience. So what you want is this:
struct A {
int n;
auto operator<=>(const A& rhs) const noexcept = default;
};
// ^^^ basically expands to vvv
struct B {
int n;
bool operator==(const B& rhs) const noexcept
{
return n == rhs.n;
}
auto operator<=>(const B& rhs) const noexcept
{
return n <=> rhs.n;
}
};
Note that you can independently default operator== while still providing a user-defined operator<=>:
struct B {
int n;
// note: return type for defaulted equality comparison operator
// must be 'bool', not 'auto'
bool operator==(const B& rhs) const noexcept = default;
auto operator<=>(const B& rhs) const noexcept
{
return n <=> rhs.n;
}
};

binary operator overloading, implicit type conversion

class my_bool {
private:
bool value;
public:
my_bool(bool value) : value(value) {}
explicit operator bool() { return value };
friend my_bool operator==(const my_bool & instance_1, const my_bool & instance_2);
friend my_bool operator&&(const my_bool & instance_1, const my_bool & instance_2);
};
void main(){
my_bool a = true;
bool b = false;
if(a == b){
// do something
}
if(a && b){
// do something
}
}
I have just created a similar topic regarding my problem over here binary operator overloading; implicit type conversion . It can be deleted I guess because it is not explicit enough about the problem I am encountering.
Why is that operator== works fine and operator&& causes ambiguities? How do I solve this problem? I can surely write down two more overloads of operator&& (bool, my_bool), (my_bool, bool). That is a messy solution though
The builtin operator&& is a context in which expressions are contextually converted to bool. Other such contexts include for example the condition of if, for, while and the conditional operator ?.
Quoting N4296, ยง4/4 (the latest publicly available draft before C++14):
Certain language constructs require that an expression be converted to a Boolean value. An expression e
appearing in such a context is said to be contextually converted to bool and is well-formed if and only if the
declaration bool t(e); is well-formed, for some invented temporary variable t (8.5).
Basically, this means that there is an "impicit explicit conversion to bool" in these contexts. Or, to illustrate this further, you can think of the following two lines being one and the same:
a && b
static_cast<bool>(a) && static_cast<bool>(b)
Therefore, the compiler must consider the explicit operator bool() when doing overload resolution for operator&&, but has to ignore it when doing overload resolution for operator== (since that operator does not force a "bool context" .. you can also compare numbers, strings, ...).
The solution in your case is IMO to get rid of the operator&&(const my_bool&, const my_bool&) all together. After all, it does not produce a more meaningful behavior than what would be possible by relying on the builtin operator&&(bool, bool). Establishing a second "boolean context" just isn't something the language was designed for (see below).
If you want to keep this operator, say for some side effects, then I see these choices:
Be explicit at the call site. That is:
if (static_cast<my_bool>(a) && static_cast<my_bool>(b)) { /* ... */ }
Be explicit at the definition site: Provide additional definitions for operator&&(my_bool const &, bool), operator&&(bool, my_bool const &). These then should rule out both operator&&(my_bool const &, my_bool const &) as well as operator&&(bool, bool) because the later are less specific. Adding these definitions to your class should mitigate the issue:
friend my_bool operator&&(const my_bool & lhs, bool rhs) {
// Delegate to operator&&(const my_bool &, const my_bool &)
return lhs && my_bool(rhs);
}
friend my_bool operator&&(bool lhs, const my_bool & rhs) {
// Delegate to operator&&(const my_bool &, const my_bool &)
return my_bool(lhs) && rhs;
}
Turns out one can "establish a boolean context", using CRTP:
#include <iostream>
using namespace std;
template<typename T>
struct bool_context {
friend T operator&&(T const & lhs, bool rhs) {
return lhs && T(rhs);
}
friend T operator&&(bool lhs, T const & rhs) {
return T(lhs) && rhs;
}
friend T operator||(T const & lhs, bool rhs) {
return lhs || T(rhs);
}
friend T operator||(bool lhs, T const & rhs) {
return T(lhs) || rhs;
}
};
struct my_bool : bool_context<my_bool> {
bool value;
my_bool(bool v) : value(v) {}
explicit operator bool() { return value; };
friend my_bool operator&&(my_bool const & lhs, my_bool const & rhs) {
cout << "my_bool::operator&&" << endl;
return lhs.value && rhs.value;
}
friend my_bool operator||(my_bool const & lhs, my_bool const & rhs) {
cout << "my_bool::operator||" << endl;
return lhs.value || rhs.value;
}
};
int main(int, char**) {
my_bool a = true;
bool b = false;
cout << "a && b => "; a && b; // my_bool::operator&&
cout << "b && a => "; b && a; // my_bool::operator&&
cout << "a && a => "; a && a; // my_bool::operator&&
cout << "b && b => "; b && b; cout << endl;
cout << "a || b => "; a || b; // my_bool::operator||
cout << "b || a => "; b || a; // my_bool::operator||
cout << "a || a => "; a || a; // my_bool::operator||
cout << "b || b => "; b || b; cout << endl;
return 0;
}
(Ideone)
My first thought on this is that the arguments to the compiler's built-in operator&& are (bool, bool), so my_bool's explicit bool operator can be invoked - since you are in effect, requesting an explicit conversion.
However, I can't find any reference in the standard as to whether a variable appearing on the right hand side of && should invoke an explicit conversion to bool.
Here's the complete error output from apple clang (once the source code above is fixed):
./nod.cpp:45:10: error: use of overloaded operator '&&' is ambiguous (with operand types 'my_bool' and 'bool')
if(a && b){
~ ^ ~
./nod.cpp:33:20: note: candidate function
friend my_bool operator&&(const my_bool & instance_1, const my_bool & instance_2);
^
./nod.cpp:45:10: note: built-in candidate operator&&(_Bool, _Bool)
if(a && b){
^
1 error generated.
So how do I fix it?
Remove the user-defined && operator.
class my_bool {
private:
bool value;
public:
my_bool(bool value) : value(value) {}
explicit operator bool() { return value; }
friend my_bool operator==(const my_bool & instance_1, const my_bool & instance_2);
// friend my_bool operator&&(const my_bool & instance_1, const my_bool & instance_2);
};
int main(){
my_bool a = true;
bool b = false;
if(a == b){
// do something
}
if(a && b){
// do something
}
}
The code bellow works in the same manner for both == and &&.
The class equality is triggered only when are of the same type.
#include <stdio.h>
class my_bool {
private:
bool v{false};
public:
my_bool() : v(v) {};
operator bool (){return v;}
friend bool operator==(const my_bool a, my_bool b){
printf("operator==\n");return a.v==b;
}
friend bool operator&&(const my_bool a, my_bool b){
printf("operator&&\n");return a.v&&b;
}
};
int main(int argc, char **argv)
{ printf("Starting\n");
bool a=true,b=true;
my_bool A{},B{},R{};
a==b;a&&b;
a==A;a&&A;
A==b;A&&b;
A==B;A&&B;
}

Weird behavior with operator defined as friend inside class

I don't understand what is going on in the following piece of code:
struct A { };
struct B {
B() { }
B(const A&) { }
friend B operator*(const B&, const B&)
{
return B();
}
};
int main()
{
B x = A() * A();
return 0;
}
When I compile (with both clang and gcc 4.9.2) I get an error message on the "B x = A() * A()" line; clang says "invalid operands to binary expression".
If I take the operator* definition from inside the class, everything is 100% ok!
struct A { };
struct B {
B() { }
B(const A&) { }
friend B operator*(const B&, const B&);
};
B operator*(const B&, const B&)
{
return B();
}
int main()
{
B x = A() * A();
return 0;
}
What is going on?
Since operator*() is defined inside the function as a friend, it can only be found by ADL, not normal unqualified lookup. This type of lookup requires that the arguments be exact types, not types that are implicitly convertible. This means the operator cannot be found even if A can be converted to B.
When you declare the function outside the class, then it can be found by the normal unqualified lookup rules.

Unintended behavior from boost::operators

I'm taking boost::operators (clang 2.1, boost 1.48.0) for a spin, and ran into the following behavior I can't explain. It seems that when I add my own operator double() const method to my class Ex (as I'd like to allow my users to idiomatically use static_cast<double>() on instances of my class), I no longer get a compiler error when trying to use operator== between dissimilar classes. In fact, it seems that operator== is not called at all.
Without operator double() const, the class works completely as expected (save for that it now lacks a conversion operator), and I receive the correct complier error when trying f == h.
So what is the right way to add this conversion operator? Code below.
// clang++ -std=c++0x boost-operators-example.cpp -Wall -o ex
#include <boost/operators.hpp>
#include <iostream>
template <typename T, int N>
class Ex : boost::operators<Ex<T,N>> {
public:
Ex(T data) : data_(data) {};
Ex& operator=(const Ex& rhs) {
data_ = rhs.data_;
return *this;
};
T get() {
return data_ * N;
};
// the troubling operator double()
operator double() const {
return double(data_) / N;
};
bool operator<(const Ex& rhs) const {
return data_ < rhs.data_;
};
bool operator==(const Ex& rhs) const {
return data_ == rhs.data_;
};
private:
T data_;
};
int main(int argc, char **argv) {
Ex<int,4> f(1);
Ex<int,4> g(2);
Ex<int,2> h(1);
// this will fail for obvious reasons when operator double() is not defined
//
// error: cannot convert 'Ex<int, 4>' to 'double' without a conversion operator
std::cout << static_cast<double>(f) << '\n';
std::cout
// ok
<< (f == g)
// this is the error I'm supposed to get, but does not occur when I have
// operator double() defined
//
// error: invalid operands to binary expression
// ('Ex<int, 4>' and 'Ex<int, 2>')
// note: candidate function not viable: no known conversion from
// 'Ex<int, 2>' to 'const Ex<int, 4>' for 1st argument
// bool operator==(const Ex& rhs) const
<< (f == h)
<< '\n';
}
You should mark your operator double() as explicit. That allows the static cast, but prevents it being used as an implicit conversion when you test for equality (and in other cases).