As Robert Wessel explains in his follow-up to the above, the core
language has to support types where e.g. `==` is not necessarily the
negation of `!=`.
Automatic definition of relational operators can to some extent be
automated via library facilities.
This can be done in a many ways.
• • •
The standard library offers a number of general operator definitions in
the `rel_ops` namespace, where it defines the other operators in terms
of `<` and `<=`. You can those definitions like this:
[code]
#include <iostream>
#include <utility> // std::rel_ops::*
using namespace std;
struct S{ int x; };
auto operator<( S const& a, S const& b )
-> bool
{ return a.x < b.x; }
auto operator==( S const& a, S const& b )
-> bool
{ return a.x == b.x; }
#define CHECK( op ) \
cout << "a " #op " b is " << (a op b) << endl;
auto main()
-> int
{
S const a{ 1 };
S const b{ 2 };
cout << boolalpha;
using namespace rel_ops;
CHECK( < ); CHECK( <= ); CHECK( == ); CHECK( >= ); CHECK( > );
cout << endl;
CHECK( != );
}
[/code]
• • •
Since the standard library places the burden of gathering operator
definitions on each piece of code using the class, it's grossly in
violation of the DRY principle: Don't Repeat Yourself.
Therefore one may define a class template that provides these operators
via the `friend` mechanism.
This is known as the Barton-Nackman trick and is, as far as I know, the
original example of the Curiously Recurring Template Pattern, or CRTP.
The detailed language rules involved have changed substantially:
originally this technique used so called friend injection, IIRC. The new
rules were designed expressly to keep code using this, still valid.
[code]
#include <iostream>
using namespace std;
template< class Derived >
struct With_relops_
{
auto self() const
-> Derived const&
{ return *static_cast<Derived const*>( this ); }
friend auto operator<=( Derived const& a, Derived const& b )
-> bool
{ return not( b < a ); }
friend auto operator!=( Derived const& a, Derived const& b )
-> bool
{ return not( a == b ); }
friend auto operator>=( Derived const& a, Derived const& b )
-> bool
{ return not( a < b ); }
friend auto operator>( Derived const& a, Derived const& b )
-> bool
{ return b < a; }
};
struct S
: With_relops_<S>
{
int x;
S( int _x = 0 ): x{ _x } {}
};
auto operator<( S const& a, S const& b )
-> bool
{ return a.x < b.x; }
auto operator==( S const& a, S const& b )
-> bool
{ return a.x == b.x; }
#define CHECK( op ) \
cout << "a " #op " b is " << (a op b) << endl;
auto main()
-> int
{
S const a{ 1 };
S const b{ 2 };
cout << boolalpha;
CHECK( < ); CHECK( <= ); CHECK( == ); CHECK( >= ); CHECK( > );
cout << endl;
CHECK( != );
}
[/code]
One side-effect of this approach is that the inheritance prevents the
class from being a Plain Old Data type, and thus, it needs a constructor
for initialization.
• • •
Instead of defining relational operators in terms of `<` and `==`, they
can be defined in terms of a `compare` function that returns an integer
result that's less than, equal to or greater than 0.
E.g. the standard library provides `std::basic_string::compare`.
This can more efficient and in the days before `std::tuple` it provided
a very easy and clear way to define lexicographic order for items with
multiple values. After `std::tuple` and `std::tie` were introduced with
C++11, only the efficiency advantage remains. Still worth knowing about:
[code]
#include <iostream>
using namespace std;
template< class Derived >
struct With_relops_
{
auto self() const
-> Derived const&
{ return *static_cast<Derived const*>( this ); }
friend auto operator<( Derived const& a, Derived const& b )
-> bool
{ return compare( a, b ) < 0; }
friend auto operator<=( Derived const& a, Derived const& b )
-> bool
{ return compare( a, b ) <= 0; }
friend auto operator==( Derived const& a, Derived const& b )
-> bool
{ return compare( a, b ) == 0; }
friend auto operator>=( Derived const& a, Derived const& b )
-> bool
{ return compare( a, b ) >= 0; }
friend auto operator>( Derived const& a, Derived const& b )
-> bool
{ return compare( a, b ) > 0; }
friend auto operator!=( Derived const& a, Derived const& b )
-> bool
{ return compare( a, b ) != 0; }
};
struct S
: With_relops_<S>
{
int x;
S( int _x = 0 ): x{ _x } {}
};
auto compare( S const& a, S const& b )
-> int
{ return a.x - b.x; } // SIMPLE & FAST BUT ASSUMING NO OVERFLOW
#define CHECK( op ) \
cout << "a " #op " b is " << (a op b) << endl;
auto main()
-> int
{
S const a{ 1 };
S const b{ 2 };
cout << boolalpha;
CHECK( < ); CHECK( <= ); CHECK( == ); CHECK( >= ); CHECK( > );
cout << endl;
CHECK( != );
}
[/code]