C ++ relational operator generator
Once you define an operator <
, you can evaluate how the rest of the relational operators behave. I am trying to implement a way to do this for my classes.
What I want is to define only <
, and the rest of the operators will be implicitly used by default. So far, I have gotten this design, which I will discuss below:
template<typename T>
struct relational
{
friend bool operator> (T const &lhs, T const &rhs) { return rhs < lhs; }
friend bool operator==(T const &lhs, T const &rhs) { return !(lhs < rhs || lhs > rhs); }
friend bool operator!=(T const &lhs, T const &rhs) { return !(rhs == lhs); }
friend bool operator<=(T const &lhs, T const &rhs) { return !(rhs < lhs); }
friend bool operator>=(T const &lhs, T const &rhs) { return !(lhs < rhs); }
};
So, for a class that implements an operator <
, it just inherits from relational
so that the rest of the operators are defaulted.
struct foo : relational<foo>
{
// implement < operator here
};
- Are there any alternatives, better designs?
-
Is there a time bomb in this code? My guess is that if the user wants to define a custom implementation for one of the operators, overload resolution will hit and select an implementation without a template (user-defined). If this is not the case (or I would have a problem with class templates inherited from
relational
), then I should implement the operators in therelational
following way:// inside the relational struct friend bool operator>(relational const &lhs, relational const &rhs) { // functions that involve implicit conversion are less favourable in overload resolution return (T const&)rhs < (T const&)lhs; }
Thanks for your advice, here's a demo of the code working
source to share
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 that needs the operators comes 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 little more complex, as it uses some simple metaprogramming to call isEqual
, not
compare
if the function exists.)
EDIT:
And rereading your question: this is basically what you are doing, and it is pretty much the standard idiom for this kind of thing. I prefer to use named functions such as compare
, but this is only personal preference. However, the metaprogramming trick for handling is isEqual
worth it that you can use the same class for types that only support equality; you will get an error when the compiler tries to instantiate eg. operator<=
but the compiler won't try to instantiate it unless someone else is using it. And it often happens that it isEqual
can be implemented much more efficiently than compare
.
EDIT 2:
For what it's worth: I do it systematically. I also have
ArithmeticOperators
(defining, for example, +
in terms +=
),
MixedTypeArithmeticOperators
(as above, but with two types T1
, for which it is the base class, and T2
; This provides the whole combination of operators). and also
STLIteratorOperators
, which implements the STL iterator interface, based on something more streamlined and (basically a GoF iterator with a isEqual
function). They save a lot of templates.
EDIT 3:
And finally: I just looked at the actual code in my toolbox. The conditional support is isEqual
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 meta-programming pattern. If the derived class defines it isEqual
, it hides it and is used. If not, this one is used.
source to share
are not inherited, so this idea won't work. However, you can use a macro instead, for example:
#define GEN(X) \
friend bool operator> (T const &lhs, T const &rhs) { return rhs < lhs; } \
friend bool operator==(T const &lhs, T const &rhs) { return !(lhs < rhs || lhs > rhs); } \
friend bool operator!=(T const &lhs, T const &rhs) { return !(rhs == lhs); } \
friend bool operator<=(T const &lhs, T const &rhs) { return !(rhs < lhs); } \
friend bool operator>=(T const &lhs, T const &rhs) { return !(lhs < rhs); }
And you can use like:
class Foo
{
...
GEN(foo)
};
source to share