14

Possible Duplicate:
Implementing comparision operators via 'tuple' and 'tie', a good idea?

sometimes I need to write some ugly functors
e.g.

lhs.date_ < rhs.date_ ||
lhs.date_ == rhs.date_ && lhs.time_ < rhs.time_ ||
lhs.date_ == rhs.date_ && lhs.time_ == rhs.time_ && lhs.id_ < rhs.id_ .....

it really annoyed me.
So I started avoiding that writing following:

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

and I am almost happy, but mind that I'm probably using tuples not by their purpose makes me worry.

Could you criticize this solution?
Or it's a good practice?
How do you avoid such comparisons?

UPDATE:
Thanks for pointing on std::tie to avoid copying objects.
And thanks for pointing on duplicate question

Community
  • 1
  • 1
bayda
  • 13,365
  • 8
  • 39
  • 48
  • 8
    `std::make_tuple` makes copies of the objects you pass it (unless you use `std::ref` or `std::cref`) -- to compare existing objects _without_ making copies, use `std::forward_as_tuple` or `std::tie` instead of `std::make_tuple`. – ildjarn May 29 '12 at 20:26
  • 1
    If you use std::tie instead it's very good practice, it makes operator< very easy to implement correctly. – Jonathan Wakely May 29 '12 at 20:33

2 Answers2

16

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.

K-ballo
  • 80,396
  • 20
  • 159
  • 169
  • IIRC "equivalent" means <= && >=. So there's that risk with tuple but it's probably safe in the situation of the OP. – djechlin May 29 '12 at 20:21
  • @djechlin: _equivalent_ means `!( lhs < rhs ) && !( rhs < lhs )`, which is equivalent to what you wrote. Why is it a risk? – K-ballo May 29 '12 at 20:22
  • Thanks. So OP should be aware this is not the same as what he was doing using ==. I don't know how OP's date class is implemented but it's conceivable to me that == would go up to the date and < or > to the second. – djechlin May 29 '12 at 20:23
  • 2
    For efficiency's sake, I would at least mention `std::tie` or `std::forward_as_tuple`. – ildjarn May 29 '12 at 20:28
  • @ildjarn: `std::tie`! That's what I was looking for... I figured explicit reference arguments to `make_tuple` wouldn't work. – K-ballo May 29 '12 at 20:29
0

You may be better off using tie instead of make_tuple (depending on whether it's worth avoiding copy/move of the members), but otherwise, this will work, and there's nothing terrible about it.

If you think about it, many POD structs are basically "named tuples", and C++ in fact caters to that by offering by-member equality comparison (and copy, move, etc.)—and in most such cases, extending it to (lexicographical) by-member less-than comparison also makes sense. But unfortunately, C++ doesn't provide any way to specify lexicographical comparison of members, except by writing it out explicitly. In a language with introspection, of course, it would be pretty easy to add, but in C++, it's not.

One obvious answer is to actually make your class actually be a named tuple. Instead of having members int date_, int64_t time_, and string id_, inherit from tuple, and write accessor methods that hide the tupleness from your users. But in this case, you've just traded ugliness in the comparison implementations for ugliness in the accessor implementations, and it's not clear that's any better.

You could use macros to wrap up the ugliness, but this probably isn't much nicer:

DEFINE_NAMED_TUPLE_CLASS(MyDate, int date, int64_t time, string id)
  ...
END_NAMED_TUPLE_CLASS

Or you could build your own external code generator that allows you to preprocess an extended C++ that has a "named_tuple_class", which it handles by just changing the tag to "class" and then defining the comparison operators. But that's a lot of work.

abarnert
  • 354,177
  • 51
  • 601
  • 671