Idiomatic Use of Std::Rel_Ops

Idiomatic use of std::rel_ops

I think that the preferred technique is not to use std::rel_ops at
all. The technique used in boost::operator (link) seems to be the usual
solution.

Example:

#include "boost/operators.hpp"

class SomeClass : private boost::equivalent<SomeClass>, boost::totally_ordered<SomeClass>
{
public:
bool operator<(const SomeClass &rhs) const
{
return someNumber < rhs.someNumber;
}
private:
int someNumber;
};

int main()
{
SomeClass a, b;
a < b;
a > b;
a <= b;
a >= b;
a == b;
a != b;
}

how to use std::rel_ops to supply comparison operators automatically?

I'd use the <boost/operators.hpp> header :

#include <boost/operators.hpp>

struct S : private boost::totally_ordered<S>
{
bool operator<(const S&) const { return false; }
bool operator==(const S&) const { return true; }
};

int main () {
S s;
s < s;
s > s;
s <= s;
s >= s;
s == s;
s != s;
}

Or, if you prefer non-member operators:

#include <boost/operators.hpp>

struct S : private boost::totally_ordered<S>
{
};

bool operator<(const S&, const S&) { return false; }
bool operator==(const S&, const S&) { return true; }

int main () {
S s;
s < s;
s > s;
s <= s;
s >= s;
s == s;
s != s;
}

Why will std::rel_ops::operators be deprecated in C++20?

In C++20, you get three-way comparison (operator <=>), which automatically "generates" default comparisons if provided:

struct A {
// You only need to implement a single operator.
std::strong_ordering operator<=>(const A&) const;
};

// Compiler generates 4 relational operators (you need to default the
// three-way comparison operator to get == and !=).
A to1, to2;
if (to1 > to2) { /* ... */ } // ok
if (to1 <= to2) { /* ... */ } // ok, single call to <=>

There are multiple advantages of the three-way comparison over std::rel_ops, which is probably why std::rel_ops operators are deprecated. On top of my head:

  • It is more versatile, since, depending on the return type of operator<=> (std::strong_ordering, std::weak_ordering, ...), only relevant operators are generated. See the <compare> header for more information.

  • You do not bring a bunch of templated operator overloads by doing using namespace std::rel_ops.

  • You can ask the compiler to generate the three-way operator for you by defaulting it (auto operator<=>(A const&) = default) — This will basically generate a lexicographic comparison of base classes and non-static data members, plus it will deduce the right type of ordering if the return type is auto.

std::rel_ops functionality as a base class. Is that appropriate solution?

Well, why not use Boost.Operators?

Why does std::rel_ops need equality operator?

Why is equality needed for rel_ops? Isn't "<" enough?

a==b => !(a<b) && !(b<a)

Because this is not true in general. If rel_ops would work only for relational operators that follow that logic it would be rather limited.

I guess what you have in mind is the weak ordering required for the < operator for associative containers. From cppreference:

Everywhere the standard library uses the Compare requirements,
uniqueness is determined by using the equivalence relation. In
imprecise terms, two objects a and b are considered equivalent (not
unique) if neither compares less than the other: !comp(a, b) &&
!comp(b, a).

In simple terms: Whether two keys are considered the "same" is only determined by requiring ! (a < b) && ! (b < a). Hence you only need to supply a < for associative containers and no operator== to decide whether two keys are the same. However, equivalence (!(a<b)&&!(b<a)) is not necessarily the same as equality (a==b).

For example when you use this

struct my_key {
int a;
int b;
bool operator< (const key_type& other) {
return a < other.a; // not comparing b !
}
};

as a key of a std::map then my_key{1,0} and my_key{1,2} are equivalent ("same key"), even though they are not equal. As another example, consider a Point in spherical coordinates where we choose to have a < b when a is closer to the origin than b:

struct Point {
double radius;
double angle;
bool operator<(const Point& other) {
return radius < other.radius;
}
bool operator==(const Point& other) {
return (radius == other.radius) && (angle == other.angle);
}
}

Also here all three a < b,b < a and a == b can be false at the same time.

Also note that (from cppreference)

As of C++20, std::rel_ops are deprecated in favor of operator<=>.

For the starship operator <=> you can choose between

std::strong_ordering 
std::weak_ordering
std::partial_ordering
std::strong_equality
std::weak_equality

Weak ordering is what is required eg for std::map (eg my_key or Point), while for strong ordering equivalence and equality are basically the same. For more details and examples I refer you to this.

if statements and relational and comparison operators: Exceptions When comparing three values/variables

The 2 < n < 1 ain't does what you think it should:

Because these operators group left-to-right, the expression a<b<c is
parsed (a<b)<c, and not a<(b<c) or (a<b)&&(b<c) - cppreference.com

The 2 < n part will return a Boolean value which in turn will be compared in the < 1.

2 < n < 1;
// equal to
(2 < n) < 1;

So, in total, the 2 < n < 1 flows like so:

  • (2 < n) < 1. Is n greater than 2? No, return false.
  • false < 1. The false is promoted to an integer, to 0. Is 0 less than 1? Yes, the if condition is true.

That's why when n == 3 in the 2 < n < 1 < 1 < 1, the overall you get false:

  • (2 < 3) < 1 < 1 < 1. Is 3 greater than 2? Yes! Returns 1 (true)
  • (1 < 1) < 1 < 1. Is 1 less than 1? No! Return 0 (false)
  • (0 < 1) < 1. Is 0 less than 1? Yes! Return 1 (true)
  • 1 < 1. Is 1 less than 1? No! Return 0 (false)

It is nonsensical as you can see. In order to make it work, you will have to make explicit checks:

(n < 2) && (n > 1)
// is n less than 2 AND is n greater than 1

Automatically deducing operators in C++?

The STL already has a set of definitions.

In the namespace stl::rel_ops the following definitions can be found.

namespace std
{
namespace rel_ops
{
// Uses ==
template <class _Tp> inline bool operator!=(const _Tp& __x, const _Tp& __y);

// Uses <
template <class _Tp> inline bool operator>(const _Tp& __x, const _Tp& __y);
template <class _Tp> inline bool operator<=(const _Tp& __x, const _Tp& __y);
template <class _Tp> inline bool operator>=(const _Tp& __x, const _Tp& __y);

} // namespace rel_ops
}

To use it you just need to do:

#include <utility>
using namespace std::rel_ops;

Though personally I would restrict the scope of the using as much as possable.

Do I need to manually declare >= and <= operators?

No, the Compiler won't declare/define any of the operators you did not define manually. However, Boost.Operators might be to your liking - it does exactly what you want the compiler to do.



Related Topics



Leave a reply



Submit