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

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;
};

Related

Ambiguity error accessing equality comparison operator in G++

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

C++17 operator==() and operator!=() code fails with C++20

I've got the following sample code:
#include <assert.h>
struct Base
{
bool operator==(const Base& rhs) const
{
return this->equalTo(rhs);
}
virtual bool equalTo(const Base& rhs) const = 0;
};
inline bool operator!=(const Base& lhs, const Base& rhs)
{
return !(lhs == rhs);
}
struct A : public Base
{
int value = 0;
bool operator==(const A& rhs) const
{
return (value == rhs.value);
}
virtual bool equalTo(const Base& rhs) const
{
auto a = dynamic_cast<const A*>(&rhs);
return (a != nullptr) ? *this == *a : false;
}
};
class A_1 : public A
{
virtual bool equalTo(const Base& rhs) const
{
auto a_1 = dynamic_cast<const A_1*>(&rhs);
return (a_1 != nullptr) ? *this == *a_1 : false;
}
};
int main()
{
A_1 a_1;
a_1.value = 1;
// Make sure different types aren't equal
A a;
a.value = 1;
assert(a_1 != a);
}
When I compile with C++17, everything is fine (no assert, as desired). Building the same code with C++20 causes the assert to fire.
How can I get this existing code to work when compiling with C++20? If I crank up the warnings, with C++20, I get 'operator !=': unreferenced inline function has been removed; I suspect this is all somehow related to operator<=>().
Is this really a known/desired breaking change from C++17 to C++20?
In C++17, the line
assert(a_1 != a);
Invokes operator!=(const Base&, const Base&), because of course, it's the only candidate. That then invokes a_1->equalTo(a), which tries to downcast a to an A_1, which in your logic gives you false. So a_1 != a evaluates as true.
In C++20, we now additionally consider rewritten candidates in terms of ==. So now we have three candidates:
Base::operator==(const Base&) const (rewritten)
A::operator==(const A&) const (rewritten)
bool operator!=(const Base&, const Base&)
Of these, (2) is the best candidate, since it's a better match for the two parameters. So a_1 != a evaluates as !((const A&)a_1 == a), which gives you a different answer.
However, your operators are fundamentally broken. Even in C++17, we had the scenario that a_1 != a evaluates as true while a != a_1 evaluates as false. This is basically the inherent problem of trying to do dynamic equality like this: this->equalTo(rhs) and rhs.equalTo(*this) might actually do different things. So this issue is less of a C++20 comparisons issue and more of a fundamental design issue.

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;
}
};

Returning const value from arithmetic operator overload with move assignment

Let's say I have the following minimal example class:
#include <iostream>
class Foo {
public:
Foo() = default;
Foo(const Foo&) = default;
Foo(Foo&&) noexcept = default;
Foo& operator=(const Foo& rhs) {
std::cout << "copy\n";
return *this;
}
Foo& operator=(Foo&& rhs) noexcept {
std::cout << "move\n";
return *this;
}
Foo operator+(const Foo& rhs) const {
Foo x; // with some calculation
return x;
}
};
int main() {
Foo a, b, c;
a = b + c;
}
This prints move as expected. Now according to Effective C++ Item 3, I should return const Foo from operator+ to avoid construct like a + b = c, i.e.:
// To avoid a + b = c
const Foo operator+(const Foo& rhs) const {}
Unfortunately, this suddenly starts calling copy assignment instead of move assignment operator. [I'm using gcc 4.8.4 on Ubuntu, but it is probably nothing related to compiler]
How can I ensure that a + b = c fails to compile and in the same time move assignment is called for a = b + c? Or with the introduction of move semantics, is there no way to achieve both of them in the same time?
I have ended up using lvalue reference qualifier as pointed by Caninonos in comment and by max66 in now deleted answer (but 10k users can see it).
Foo& operator=(const Foo& rhs) & {}
Foo& operator=(Foo&& rhs) & noexcept {}
It is simple to implement and it provides a better interface design since assignment to anything other that lvalue doesn't sound meaningful and a possible source of bug.
However, it should be noted that the possibility of writing a + b = c by mistake is very low. Also compiler generated assignment operators are not lvalue reference qualified and we can write a + b = c with standard types, e.g. with std::string or with std::complex.

possible implementations of casting in c++

I have this snippet of the code in my header:
class A {
private:
int player;
public:
A(int initPlayer = 0);
A(const A&);
A& operator=(const A&);
~A();
void foo() const;
friend int operator==(const A& i, const A& member) const;
};
implementation of the operator==
int operator==(const A& i, const A& member) const{
if(i.player == member.player){
return 1;
}
return 0;
}
and I need casting for this part of my code:
i - is some int, which my function receives
A *pa1 = new A(a2);
assert(i == *pa1);
I receive an error non-member function, How can I fix it? thanks in advance
Your error is nothing to do with casting or user-defined conversions.
You can't have a const qualification on a function that isn't a member function so this:
int operator==(const A& i, const A& member) const;
should be this:
int operator==(const A& i, const A& member);
Remove the const qualifier from the friend function. Friend functions are not member functions hence the const qualifier is meaningless.
There are two solutions:
Make operator==() a member function, not a friend:
Interface:
class A {
private:
int player;
public:
A(int initPlayer = 0);
A(const A&);
A& operator=(const A&);
~A();
void foo() const;
int operator==(const A& rhv) const;
};
Implementation:
int A::operator==(const A& rhv) const
{
return this->player == rhv.player;
}
If you really want operator==() as a friend function, just remove the const qualifier, as mentioned in the other posts.