patterncppMinor
A Mixin Comparator class
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
Implementation: (please review this part, requires C++14)
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.
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
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
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:
We can instead write equality like so:
Note that the operator here takes
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:
Cross-Compare
If two classes have comparable
Go wild.
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.