I'm struggling with providing the new spaceship-operator for a enum class. Lets take the following example:
#include <cstdio>
#include <iostream>
#include <compare>
#include <cstdint>
enum class Animals : uint8_t
{
Bird = 27, //those values are just for making a point
Tiger = 5,
Ant = 100,
Snake = 45,
Wale = 17
};
//auto operator<=(const Animals& lhs, const Animals& rhs) = delete;
//auto operator>=(const Animals& lhs, const Animals& rhs) = delete;
//auto operator<(const Animals& lhs, const Animals& rhs) = delete;
//auto operator>(const Animals& lhs, const Animals& rhs) = delete;
auto operator<=>(const Animals& lhs, const Animals& rhs)
{
std::cout << "comparing via overloaded <=> operator\n";
//order the animals by their size in real life
// Ant < Bird < Snake < Tiger < Wale
//for this MVCE only Ant < Tiger is in here:
if(lhs == Animals::Ant && rhs == Animals::Tiger)
return -1;
return 0; //all unimplemented ones are treated as equal
}
int main(void)
{
if(Animals::Ant < Animals::Tiger)
std::puts("success (do I see the prompt of the overloaded operator?)");
else
std::puts("seems to use uint8_t comparison instead");
return 0;
}
But obviously I'm getting something wrong in here, as my main() still tells me, that Ants are bigger than Tigers. As you can see I tried to explicitly delete the default comparison-operators, to force the compiler to use my custom-spaceship-one, but without success.
When I explicit call auto result = Animals::Ant <=> Animals::Tiger I get an ambiguous overload for 'operator<=>' (operand types are 'Animals' and 'Animals'). But that seems to be related to my operators signature (using const Animals instead).
Is it possible to overwrite the operator for my Enum (without interfering with the operators for its basic type "uint8_t"?
There are two things wrong with your operator<=>:
it needs to take the enums by value, not by reference-to-const, in order to the suppress the built-in candidate
it needs to return one of the comparison categories, not int. In this case probably std::weak_ordering is right.
That is:
constexpr auto operator<=>(Animals lhs, Animals rhs) -> std::weak_ordering
{
//order the animals by their size in real life
// Ant < Bird < Snake < Tiger < Wale
//for this MVCE only Ant < Tiger is in here:
if(lhs == Animals::Ant && rhs == Animals::Tiger)
return std::weak_ordering::less;
return std::weak_ordering::equivalent;
}
That said, there is implementation divergence for how to handle rewrite candidates with <=>. clang and msvc implement what the rules probably should be, which is that our user-declared operator<=> suppresses all the built-in relational and three-way comaprison operators, so that Animals::Ant < Animals::Tiger invokes our operator<=>. But gcc implements what the rules technically actually literally say, which is that Animals::Ant <=> Animals::Tiger evaluates our operator but using < does not. There is a gcc bug report open for this (#105200), where one of the gcc developers points out the wording issue. This strikes me as a wording issue, rather than an actual design intent issue, so I'm opening a Core issue about this (#205).
In order for this to work on gcc, you have to also go through and add these yourself (note: always by value):
constexpr auto operator<(Animals lhs, Animals rhs) -> bool {
return (lhs <=> rhs) < 0;
}
constexpr auto operator<=(Animals lhs, Animals rhs) -> bool {
return (lhs <=> rhs) <= 0;
}
constexpr auto operator>(Animals lhs, Animals rhs) -> bool {
return (lhs <=> rhs) > 0;
}
constexpr auto operator>=(Animals lhs, Animals rhs) -> bool {
return (lhs <=> rhs) >= 0;
}
Related
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.
In a slight variation of this question. I would like to define a custom type with a custom <=> operator and use that custom <=> operator to generate a ==. Trying the following
#include <compare>
#include <iostream>
#include <cassert>
struct widget {
int member;
int non_comparison_data;
friend std::strong_ordering operator<=>(const widget& lhs,
const widget& rhs) {
std::cout << "doing a three way comparison" << std::endl;
return lhs.member <=> rhs.member;
}
// friend bool operator==(const widget& lhs, const widget& rhs) {
// return 0 == (lhs <=> rhs);
// }
friend bool operator==(const widget& lhs, const widget& rhs) = default;
};
int main() {
widget a{.member = 1, .non_comparison_data = 23};
widget b{.member = 1, .non_comparison_data = 42};
assert(a==b);
return 0;
}
I observe that a default == operator does not use the custom <=> operator, but instead does what would be done in absence of any custom comparison and compares all data members. I can define an operator == on my own that uses <=> like follows, but I'm wondering if there's a better way to get == out of <=>.
friend bool operator==(const widget& lhs, const widget& rhs) {{
return 0 == (lhs <=> rhs);
}
PS: compiler-explorer link
PS: I'm aware a custom <=> doesn't generate a default == because == is likely implementable in a more optimal way than using <=> and one does not want an inefficient default to be generated.
I'm wondering if there's a better way to get == out of <=>.
Nope, there's not. What you did (return 0 == (lhs<=>rhs);) is the optimal way. What more were you looking for?
The only way to define == in terms of <=> is to do it manually. Which is what you're already doing.
You can do a little bit better by writing member functions (the only benefit of non-member friends would be if you wanted to take by value. But if you're not doing that, member functions are easier - simply less code to write). And then implement == forwards instead of backwards... no Yoda conditionals please.
struct widget {
int member;
int non_comparison_data;
std::strong_ordering operator<=>(const widget& rhs) const {
return member <=> rhs.member;
}
bool operator==(const widget& rhs) const {
return (*this <=> rhs) == 0;
}
};
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
I have simply declared a structure like this -
struct data{
int x,y;
};
Now I have declared 2 variables a & b of data type. I've assigned appropriate values to them. Now, I want to check if they are equal! I am trying to do like this -
data a,b;
a.x=12, a.y=24;
b.x=15, b.y=30;
if(a!=b)cout<<"a~b"<<endl;
But the compiler is giving me the following error on the 4th line ->
error: no match for 'operator!=' (operand types are 'data' and 'data')
Where is the problem actually? Isn't this compare supported in C++?? Or I'm making any mistakes??
What is the exact and easiest way to do this?? Do I need to compare each of the elements in the structure separately?? Or there's any other smarter way??
C++ gives you attribute-by-attribute assignment implicitly, but no comparison for equality or ordering. The reason is "just because", don't look too hard into philosophy.
You must to provide those operators, if needed, by implementing them yourself explicitly, for example:
bool operator<(const Data& other) const {
if (x < other.x) return true;
if (x > other.x) return false;
return y < other.y;
}
bool operator==(const Data& other) const {
return x == other.x && y == other.y;
}
and so on.
Note also that defining for example == doesn't give you != automatically and defining < doesn't provide >= implicitly.
UPDATE
C++20 introduces (will introduce) a new operator <=> (friendly name "spaceship operator") exactly to remove the verbosity of having to define all possible relational operators. In this case adding:
std::strong_ordering operator<=>(const Data& other) const {
if (auto cmp = x <=> other.x; cmp != 0) return cmp;
return y <=> other.y;
}
will allow compilation of all relational tests (<, <=, >, >=, ==, !=) between elements of the class based on checking x first and, if that check doesn't resolve, checking y instead.
You have to implement all operators explicitely that you intent to use. In your case, you will need to supply bool operator!=(const data&, const data&).
A nice way to implement it for PODs like this is to use std::tuple since it already implements ordering:
#include <tuple>
// ...
bool operator!=(const data& p_lhs, const data& p_rhs)
{
return std::tie(p_lhs.x, p_lhs.y) != std::tie(p_rhs.x, p_rhs.y);
}
std::tie (documentation) creates a temporary tuple of references. Those two tuples can then be compared, since std::tuple defines all comparison operators, as shown here.
I chose to implement operator!= as a free function. You can, of course, choose to implement it as member of your class:
struct data
{
bool operator!=(const data& p_rhs) const
{
return std::tie(x, y) != std::tie(p_rhs.x, p_rhs.y);
}
int x, y;
};
Of course you should define all other operators, too. Remember that you can implement most operators by delegating to others.
Automatic C++ comparisons are coming in C++20, so you can just add a special operator to indicate that you need default comparisons when new standard is out.
class Point {
int x;
int y;
public:
auto operator<=>(const Point&) const = default;
// ... non-comparison functions ...
};
https://en.cppreference.com/w/cpp/language/default_comparisons
You have to implement bool operator != (const data&, const data&);.
Possible implementation (in c++11):
#include <tuple>
//...
bool operator == (const data& lhs, const data& rhs) {
return std::tie(lhs.x, lhs.y) == std::tie(rhs.x, rhs.y);
}
bool operator != (const data& lhs, const data& rhs) {
return !(lhs == rhs);
}
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); }