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 the relational

    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

+2


source to share


2 answers


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.

+4


source


Friends

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)
};

      

+1


source







All Articles