Implementing Comparison Operators Via 'Tuple' and 'Tie', a Good Idea

Implementing comparison operators via 'tuple' and 'tie', a good idea?

This is certainly going to make it easier to write a correct operator than rolling it yourself. I'd say only consider a different approach if profiling shows the comparison operation to be a time-consuming part of your application. Otherwise the ease of maintaining this should outweigh any possible performance concerns.

using make_tuple for comparison

The declaration of std::tuple comparison operators states that:

Compares lhs and rhs lexicographically, that is, compares the first elements, if they are equivalent, compares the second elements, if those are equivalent, compares the third elements, and so on.

So what you are doing, other than having the possibility of creating unneeded temporaries (probably optimized away), seems ok to me.

Note that equivalent means !( lhs < rhs ) && !( rhs < lhs ) and not lhs == rhs, that's equality. Assuming equivalent and equality mean the same for your class, this would be ok. Note that this is no different than, for instance, accessing a set/map by key.

In order to avoid the temporaries, you can use std::tie which makes a tuple of lvalue references to its arguments:

std::tie( lhs.date_, lhs.time_, lhs.id_ ) < 
std::tie( rhs.date_, rhs.time_, rhs.id_ );

In a similar fashion, std::forward_as_tuple makes a tuple of rvalue references.

Is using std::tuple to implement operator<, = etc. efficient and correct?

It's correct, but it's not efficient, because the constructor of std::tuple will copy all the values. This also means you can't use it to compare non-copyable types.

Instead you could use std::tie, which needs a bit less code and doesn't copy the values, but creates a tuple of references.

Your operator< would then become:

bool operator<(const Customer& rop) const {
return std::tie(name, age) < std::tie(rop.name, rop.age);
}

c++11 combining std::tuple and std::tie for efficient ordering

I just discovered that the problem with the 3rd lambda was that I was not correctly comparing the elements correctly for equivalence and lexicographical comparison. The correct approach is outlined in bullet 3) for tuple comparison where it indicates that I should use the following approach.

3) (bool)(std::get<0>(lhs) < std::get<0>(rhs)) ||
(!(bool)(std::get<0>(rhs) < std::get<0>(lhs)) && lhstail < rhstail),
where lhstail is lhs without its first element, and rhstail is rhs
without its first element. For two empty tuples, returns false.

The fixed up lambda comparator for sorting firsty sorts according to the filename() temporary and then uses the efficient std::tie for the other elements in the tuple

std::cout << "Order[mPath.filename(), elem1, intVal]" << std::endl;
std::cout << "======================================" << std::endl;
std::sort(aStructs.begin(), aStructs.end(),
[](const StructA& lhs, const StructA& rhs){
// attempt at efficiency - but not quite right
// AHA, I think I figured it out - see tuple operator_cmp
// return value documentation which states
// (bool)(std::get<0>(lhs) < std::get<0>(rhs)) ||
// (!(bool)(std::get<0>(rhs) < std::get<0>(lhs)) &&
// lhstail < rhstail), where lhstail is lhs without
// its first element, and rhstail is rhs without its
// first element. For two empty tuples, returns false.
// --------------------------------------------------------
// edit thanks to @Praetorian for suggesting the following:
// --------------------------------------------------------
auto f1 = lhs.mPath.filename(); auto f2 = rhs.mPath.filename();
return std::tie(f1, lhs.elem1, lhs.intVal) < std::tie(f2, rhs.elem1, rhs.intVal);
});

Doing so made the first set of results identical to the 3rd - not incredibly efficient for the filename() temporary but at least I don't take the std::make_tuple hit for all elements in my struct. The updated live example is here

Implementing other comparison operators in terms of operator< in one call

  • a > b == b < a
  • a <= b == !(b < a)
  • a >= b == !(a < b)

It's even possible to implement equality in terms of less than (Kind of abusing my meta-syntax here):

  • (a == b) == (!(a < b) && !(b < a))
  • (a != b) == (a < b || b < a)

Although I wouldn't suggest doing so in practice, since it requires two comparisons and can generally be implemented more efficiently directly.

Implementing the '<' operator

You could use std::tie.

class Person
{
public:
bool valid = false;
std::string name = "";
std::string id = "";

bool operator<(const Person& r) const
{
return std::tie(valid, name, id) < std::tie(r.valid, r.name, r.id);
}
}

Explanation:

std::tie(xs...) creates an std::tuple of references to the passed xs... arguments. Comparison between two std::tuple instances works by lexicographically comparing the elements, thus providing an ordering for your type.

More information here on cppsamples and in this question.

Compare tuples of different sizes

operator== requires the tuples to be of equal lengths.

§ 20.4.2.7 [tuple.rel]:

template<class... TTypes, class... UTypes>
constexpr bool operator==(const tuple<TTypes...>& t, const tuple<UTypes...>& u);

1 Requires: For all i, where 0 <= i and i < sizeof...(TTypes), get<i>(t) == get<i>(u) is a valid expression returning a type that is convertible to bool. sizeof...(TTypes) == sizeof...(UTypes).

If you want two tuples of different lengths to be considered unequal, you'd need to implement this logic yourself:

template <typename... Ts, typename... Us>
auto compare(const std::tuple<Ts...>& t1, const std::tuple<Us...>& t2)
-> typename std::enable_if<sizeof...(Ts) == sizeof...(Us), bool>::type
{
return t1 == t2;
}

template <typename... Ts, typename... Us>
auto compare(const std::tuple<Ts...>& t1, const std::tuple<Us...>& t2)
-> typename std::enable_if<sizeof...(Ts) != sizeof...(Us), bool>::type
{
return false;
}

DEMO

This way, the code comparing two tuples, t1 == t2, is instantiated only when the lengths of tuples match each other. In your scenario, a compiler is unable to compile your code, since there is no predefined operator== for such a case.



Related Topics



Leave a reply



Submit