Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
403 views
in Technique[技术] by (71.8m points)

templates - C++ relational operators generator

Once you define the < operator, you can have an estimation of how the rest of relational operators behave. I'm trying to implement a way to do that for my classes.

What I want is to define only the < and the rest of the operators to be defaulted implicitly. What I've got so far is this design, which I'll elaborate on further down:

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 the < operator it would just take inheriting from relational to have the rest of the operators defaulted.

struct foo : relational<foo>
{ 
    // implement < operator here
};
  1. Are there any alternatives, better designs ?
  2. Is there a time bomb in this code? I'm assuming that if a user wants to define a custom implementation for one of the operators, the overload resolution would kick and select the non template (user defined) implementation. If that's not the case (or I would have problem with class templates inheriting from relational) should I implement the operators in relational like this ?

    // 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 advices, here's a demo of the code working

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...