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 isauto
.
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 nota<(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, returnfalse
.false < 1
. Thefalse
is promoted to an integer, to 0. Is 0 less than 1? Yes, theif
condition istrue
.
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! Returns1
(true
)(1 < 1) < 1 < 1
. Is 1 less than 1? No! Return0
(false
)(0 < 1) < 1
. Is 0 less than 1? Yes! Return1
(true
)1 < 1
. Is 1 less than 1? No! Return0
(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
Does Scopeguard Use Really Lead to Better Code
Differentiate Between a Unix Directory and File in C and C++
Std Linker Error with Apple Llvm 4.1
What Is Difference Between Const and Non Const Key
Lambdas Require Capturing 'This' to Call Static Member Function
Visual Studio 2013 Error Ms8020 Build Tools V140 Cannot Be Found
C++ Meta-Programming Doxygen Documentation
C++ Trying to Get Function Address from a Std::Function
C++ Reading Unsigned Char from File Stream
Send Email with Attachment in C++
Aligned_Storage and Strict Aliasing
Should I Return an Rvalue Reference (By Std::Move'Ing)
Is There a Null Std::Ostream Implementation in C++ or Libraries
Why Does Initializing an Extern Variable Inside a Function Give an Error