HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

A Mixin Comparator class

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
mixinclasscomparator

Problem

Idea:
In C++, often a straightforward implementation of comparison operators is needed. In C++11, they can be conveniently implemented using std::tie. The following mixin class automatically enables comparison operators by deriving a class T from Comparable.

Implementation: (please review this part, requires C++14)

// A mixin class that makes derived classes comparable
template
struct Comparable {
    // nothing is needed here
};

// Default: Assume the client class offers a tie method
// Alternative: The client class can override this method
template
auto tie(const Comparable& a_object)
{
    return static_cast(a_object).tie();
}

template
bool operator==(const Comparable& a_lhs, const Comparable& a_rhs)
{
    return tie(a_lhs) == tie(a_rhs);
}

template
bool operator!=(const Comparable& a_lhs, const Comparable& a_rhs)
{
    return tie(a_lhs) != tie(a_rhs);
}

template
bool operator& a_lhs, const Comparable& a_rhs)
{
    return tie(a_lhs) 
bool operator>(const Comparable& a_lhs, const Comparable& a_rhs)
{
    return tie(a_lhs) > tie(a_rhs);
}

template
bool operator& a_lhs, const Comparable& a_rhs)
{
    return tie(a_lhs) 
bool operator>=(const Comparable& a_lhs, const Comparable& a_rhs)
{
    return tie(a_lhs) >= tie(a_rhs);
}


Usage Example: Client code then could look like the following. The implementation of the tie function automatically defines a lexicographical ordering for all supported operations.

struct Client : public Comparable
{
    auto tie() const
    {
        return std::tie(m_a, m_b);
    }

    int m_a = 1;
    int m_b = 2;
};

Client a, b;
b.m_b = 5;

const bool isEqual = (a == b);
const bool isNotEqual = (a != b);
const bool isSmaller = (a  b);
const bool isSmallerOrEqual = (a = b);


Advantage: There is no need anymore to implement many comparison operators by hand for many classes individually. If there are many different classes requiring comparison operators with straightforward semantics this can save quite some cod

Solution

ADL

This can be improved. First - you're requiring a tie() member function, it'd be better to make that more of a customization point. Second - you're putting all the operators in global scope when it'd be better to hide them. Both issues can be solved with some ADL.

The advantage of making an operator a non-member friend declared inline is that it can only be found when comparing precisely what you want. The downside to what you're doing right now is the following compiles:

struct B : Comparable {
    ...
};

struct D {
    operator B() { ... }
};

D d1 = ...;
D d2 = ....;
if (d1 == d2) { ... }


D isn't comparable - but this code would still compile via the B conversion, which may be surprising!

We can instead write equality like so:

namespace cmp {
    template 
    auto tie(const T& val) -> decltype(val.tie()) {
        return val.tie();
    }

    template 
    struct Comparable {
        friend bool operator==(const T& lhs, const T& rhs) {
            return tie(lhs) == tie(rhs);
        }
    };
}


Note that the operator here takes Ts and not Comparables. We know what the derived type is, so we can just use it. The nonqualified call to tie lets us write something like:

namespace foo {
    struct B : Comparable {
        int i;
    };

    int tie(const B& b) { return b.i; }
}


Customization is nice.

Split it up

EqualityComparable and LessThanComparable are different concepts. If you split up the responsibilities, it'll make your class more useful:

namespace cmp {
    template 
    struct EqualityComparable {
        friend bool operator==(const T& lhs, const T& rhs) { return tie(lhs) == tie(rhs); }
        friend bool operator!=(const T& lhs, const T& rhs) { !(lhs == rhs); }
    };

    template 
    struct LessThanComparable {
        friend bool operator
    struct TotallyOrdered : EqualityComparable, LessThanComparable
    { };
}


Cross-Compare

If two classes have comparable tie()s, why not support that too?

template 
struct EqualityComparable2
{
    friend bool operator==(const T& lhs, const U& rhs) { return tie(lhs) == tie(rhs); }
    friend bool operator==(const U& lhs, const T& rhs) { return rhs == lhs; }
    // same for !=
};


Go wild.

Code Snippets

struct B : Comparable<B> {
    ...
};

struct D {
    operator B() { ... }
};

D d1 = ...;
D d2 = ....;
if (d1 == d2) { ... }
namespace cmp {
    template <class T>
    auto tie(const T& val) -> decltype(val.tie()) {
        return val.tie();
    }

    template <class T>
    struct Comparable {
        friend bool operator==(const T& lhs, const T& rhs) {
            return tie(lhs) == tie(rhs);
        }
    };
}
namespace foo {
    struct B : Comparable<B> {
        int i;
    };

    int tie(const B& b) { return b.i; }
}
namespace cmp {
    template <class T>
    struct EqualityComparable {
        friend bool operator==(const T& lhs, const T& rhs) { return tie(lhs) == tie(rhs); }
        friend bool operator!=(const T& lhs, const T& rhs) { !(lhs == rhs); }
    };

    template <class T>
    struct LessThanComparable {
        friend bool operator<(const T& lhs, const T& rhs) { return tie(lhs) < tie(rhs); }
        friend bool operator<=(const T& lhs, const T& rhs) { return !(rhs < lhs); }
        // etc.
    };

    template <class T>
    struct TotallyOrdered : EqualityComparable<T>, LessThanComparable<T>
    { };
}
template <class T, class U>
struct EqualityComparable2
{
    friend bool operator==(const T& lhs, const U& rhs) { return tie(lhs) == tie(rhs); }
    friend bool operator==(const U& lhs, const T& rhs) { return rhs == lhs; }
    // same for !=
};

Context

StackExchange Code Review Q#112430, answer score: 2

Revisions (0)

No revisions yet.