I usually use a trick I learned from Robert Martin to do this.
I have a template class:
template <typename T>
class ComparisonOperators
{
protected:
~ComparisonOperators() {}
public:
friend bool operator==( T const& lhs, T const& rhs )
{
return lhs.compare( rhs ) == 0;
}
friend bool operator!=( T const& lhs, T const& rhs )
{
return lhs.compare( rhs ) != 0;
}
friend bool operator<( T const& lhs, T const& rhs )
{
return lhs.compare( rhs ) < 0;
}
friend bool operator<=( T const& lhs, T const& rhs )
{
return lhs.compare( rhs ) <= 0;
}
friend bool operator>( T const& lhs, T const& rhs )
{
return lhs.compare( rhs ) > 0;
}
friend bool operator>=( T const& lhs, T const& rhs )
{
return lhs.compare( rhs ) >= 0;
}
};
The class which needs the operators derives from this:
class Toto : public ComparisonOperators<Toto>
{
// ...
public:
// returns value < 0, == 0 or >0, according to
// whether this is <, == or > other.
int compare( Toto const& other ) const;
};
(My implementation is actually a bit more complicated, since it
uses some simple meta-programming to call isEqual
, rather than
compare
, if that function exists.)
EDIT:
And rereading your question: this is basically what you're doing, and it's pretty much the standard idiom for this sort of thing. I prefer using named functions like compare
, but that is just a personal preference. The meta-programming trick to handle isEqual
, however, is worth the bother: it means that you can use the same class for types which only support equality; you'll get an error when the compiler tries to instantiate e.g. operator<=
, but the compiler won't try to instantiate it unless someone uses it. And it's often the case the isEqual
can be implemented a lot more efficiently than compare
.
EDIT 2:
For what it's worth: I do this systematically. I also have
ArithmeticOperators
(defining e.g. +
in terms of +=
),
MixedTypeArithmeticOperators
(like the above, but with two
types, T1
, for which it is a base class, and T2
; it
provides all of the combination of operators). and
STLIteratorOperators
, which implements the STL iterator
interface based on something more rational and easier to
implement (basically, the GoF iterator with an isEqual
function). They saves a lot of boilerplate.
EDIT 3:
And finally: I just looked at the actual code in my toolkit.
Conditionally supporting isEqual
is even simpler than
I remembered: the template class above has a public member:
bool isEqual( T const& other ) const
{
return static_cast< T const* >( this )->compare( other ) == 0;
}
And operator==
and operator!=
just use isEqual
, no
template meta-programming involved. If the derived class
defines an isEqual
, it hides this one, and it gets used. If
not, this one gets used.