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

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.

Related

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

boost operator totally_ordered composed of less_than_comparable and equality_comparable

As boost operator document say,template totally_ordered is composed of template less_than_comparable and tempalte equality_comparable.
It means that if a class inherents from template totally_ordered, operator== must be implemented when using operator== or operator!=.
In my view, if operator< is implemented, operator== can be generated automatically like (!(lhs < rhs) && !(rhs < lhs)).So, is operator== necessary ?
code piece:
#include <boost/operators.hpp>
class Foo : public boost::totally_ordered<Foo>
{
public:
explicit Foo(const int num) : m_nMem(num){}
friend bool operator< (const Foo& lhs, const Foo& rhs)
{
return lhs.m_nMem < rhs.m_nMem;
}
// Is operator== necessary ?
// Is operator== equal to (!(lhs < rhs) && !(rhs < lhs)) ?
//friend bool operator== (const Foo& lhs, const Foo& rhs)
//{
// return lhs.m_nMem == rhs.m_nMem;
//}
private:
int m_nMem;
};
int main()
{
Foo foo_1(1), foo_2(2);
foo_1 == foo_2; // compiler error , no operator==
return 0;
}
A strict weak ordering may rate unequal elements equivalent¹
E.g.:
struct Point {
int x,y;
bool operator<(Point const& other) const { return x < other.x; }
};
Here, Points would be ordered by x, and points having equal x would be equivalent according to your suggested implementation.
However, since y may be different, clearly the points are not guaranteed to be equal.
Only if the comparison is in fact a total ordering, then we can generate the equality operation using the relative comparison operators. I can only suspect the library authors
wanted the users to be very conscious of this implications
realized that using (!(lhs < rhs) && !(rhs < lhs)) might lead to suboptimal performance
¹ https://www.sgi.com/tech/stl/StrictWeakOrdering.html

How to overload operator ==?

I have class A
how to overload operator == to perform
A a,b,c;
if (a==b==c) {}
Could anyone help me?
Very short answer: NO!
Slightly longer answer: Don't try this.
Explanation: Every C++ programmer is used to have the comparison operator return a bool or something convertible to bool. Just because it's natural to type things like if (a==b).
So if the expression a==b returns a bool x, then a==b==c would mean comparing x with c. Which makes no sense at all. Even if you get it to compile, e.g. comparing ints that way, it would not yield the result you expect.
So, while there technically I can think of a solution to what you seem to want (compare if all three are equal), the right thing to do is how it is always done in C++: logically chain binary comparisons:
if (a==b && b==c) {}
for the ones who wonder about the "technical doable but oh so ugly" solution:
(DON'T DO THIS IN REAL WORLD USE! You should get fired if you do.)
template <class T>
struct multiCompareProxy {
bool b;
T const& t;
explicit operator bool() const {return b;}
multiCompareProxy operator==(T const& rhs) {
return {b && t.equals(rhs), t};
}
};
template <class T>
multiCompareProxy<T> operator==(T const& lhs, T const& rhs) {
return {lhs.equals(rhs), lhs};
}
A class now just has to overload the euqals method for this to work: Example
If for whatever reason you really wanted to do this, you'd need a proxy object like so:
struct A {};
struct Proxy {};
Proxy operator==(const A& a, const A& b) {
return {};
}
bool operator==(const Proxy& p, const A& b) {
return true;
}
bool operator==(const A& a, const Proxy& p) {
return true;
}
#include <iostream>
int main() {
A a, b, c;
if(a == b == c) {
std::cout << "Bad.\n";
}
}
But don't do this. Use (a == b) && (a == c) like everyone else.
It can be done, and it's very occasionally useful as a kind of domain-specific language (DSL) for matching the notation expected by non-C++-programmers, but it should be studiously avoided for other uses as it'll confound (and annoy) programmers working on the code if this is used willy-nilly.
class A
{
// ...
bool equals(const A& rhs) const { return ... }
struct X
{
X(const A& a, bool b) : a_(a), b_(b) { }
X& operator==(const A& rhs) const { b_ &= a_.equals(rhs); return *this; }
explicit operator bool() const { return b_; }
// remove explicit pre C++11 / consider making it operator void*() ...
const A& a_;
mutable bool b_;
};
X A::operator==(const A& rhs) const
{
return X(*this, equals(rhs));
}
};
(You might prefer standalone functions if you have implicit constructors).
The same kind of proxy-using hackery allows all manner of unexpected notations like 3 < x < 9 and x == a1 || a2 || a3... again, avoid them unless it'll make a huge difference to the maintainers of the code where they'll be used (and ideally that code will have very clear boundaries from the other C++ code in your system).
As an example of good uses of such techniques, consider the boost spirit library and it's unusual use of a lot of operators to provide something approximating BNF notation....
You cannot (minus the possibility of an ugly hack) overload operator==(...) to work as you have specified.
If you think about what it would do a == b would become either true or false (a bool), then you would have (<bool> == c) left. The proper way to do what you are looking to do would be some form of (a==b) && (b==c).
There are times when you can overload an operator to work as you describe, but it would have to return the same type that it takes. An example would be:
class A
{
A& operator+=(A const& rhs)
{
// operator logic
return *this;
}
}
This case works because with a += b += c, you would perform (b += c) which would return an A&, which the remaining a.operator+=(...) accepts as an argument.
bool operator==(T const & a, T const & b) {
return /*boolean expr*/
}
if you have a class you can do:
class MyClass {
public:
bool operator==(T const & rhs) const {
return /*boolean expr*/
}
}
And don't use == twice in a row, you can't, do:
a == b && b == c
I would write:
bool operator==(const A& lhs, const A& rhs){ /* do actual comparison */ }
And use it twice with and operation.

Practical reason for defining operator!=

The following code will fail to compile under GCC because it does define operator== but does not define operator!=.
struct A {
unsigned int m_i;
bool operator == (const A& rhs) const { return m_i == rhs.m_i; }
};
bool f(const A& lhs, const A& rhs) { return lhs != rhs; }
Obviously it wants either
bool operator != (const A& rhs) const { return !(operator==(rhs)); }
or
bool operator != (const A& rhs) const { return m_i != rhs.m_i; }
Common wisdom seems to be that this is because !operator== adds an instruction and so is less efficient. This leads some programmers to dutifully write out their complex != expression in full, and over the years I've fixed a number of bugs resulting from mismatched operators.
Is this coercion to write both operators a case of premature/legacy optimization, or is there a good, solid, practical reason to do this code-doubling that I'm just somehow missing ?
I would say absent some overwhelming evidence to the contrary, it's purely premature optimization (not even legacy--I doubt there was ever a good reason for it, at least in anything approaching a C++ time-frame).
For what it's worth, §20.2.1 of the C++ standard defines a number of overloads in <utility> that will give you a != based on operator== and a >, >=, <= all based on operator<.
Why not use this:
bool f(const A& lhs, const A& rhs) { return !(lhs == rhs); }

Quick and dirty operator!=

In my classes I often write a quick operator!= by returning !(*this == rhs), e.g.:
class Foo
{
private:
int n_;
std::string str_;
public:
...
bool operator==(const Foo& rhs) const
{
return n_ == rhs.n_ && str_ == rhs.str_;
}
bool operator!=(const Foo& rhs) const
{
return !(*this == rhs);
}
};
I can't see any obvious problems with doing this but thought I'd ask if anyone knows of any.
I believe that's the preferred method of implementing operator!= so that you don't repeat yourself, and you have a guaranteed correct relationship with operator==.
Defining operator!= as !operator== is just fine
For getting these trivial equivalent operators easily defined, I always use Boost.Operators.
The case with only operator== and operator!= (i.e. using equality_comparable<>) doesn't gain very much.
But when you need less and greater than too, or some combination of operator+, operator* etc. this becomes very convenient.
An example for your case would read
class Foo : private boost::equality_comparable< Foo >
{
private:
int n_;
std::string str_;
public:
...
bool operator==(const Foo& rhs) const
{
return n_ == rhs.n_ && str_ == rhs.str_;
}
};
No, that's absolutely fine - I do exactly the same.