Thursday, April 26, 2012

std::tie and Strict Weak Ordering


One of the requirements for placing objects into a std::map or std::set is that the Key type has Strict Weak Ordering, usually via std::less. Writing a suitable comparator for an abritary structure is a pretty trivial excercise. The problem is,  it is also trivially easy to make a mistake.
If the comparator is not Strict Weak, there will be no compiler warning or alarm bells. Worse still, if you are unlucky, there will be no indication at runtime either. Just occasional, seemingly at random, inserting or deleting an item will segfault.
If only we didn't have to write these trivial comparators ourselves!

The C++ Standard Committe has a paper n3326 on compile time reflection that contains a hidden little gem. The tuple helper function std::tie can be used to write a comparator function for you!

A quick reminder, std::tie is a shorthand way of binding seperate elements to a tuple.
tuple< int, int > coords( 1, 2 );
int x;
int y;
tie( x, y ) = coords;

The trick to tie is that it creates a tuple of references to elements - its the reference version of make_tuple. Usefully a tuple defines the full set of comparison operators ==, <, >, >=, <= and !=. The comparison operators are not restricted to the exact same tuple type either, but work with any tuple where each corresponding member is comparable.

It's the ability to tie references into a tuple combined with the tuple comparison operators that allows the shorthand trick. Consider the following struct:

struct Burger {
  int  bacon;
  int  meat; 
  int  green_stuff;
  string name;
};
Thanks to std::tie we can easily write a map friendly operator<:

bool operator<( const Burger& l, const Burger& r ) {
  return tie( l.bacon, l.meat, l.green_stuff, name ) < tie( r.bacon, r.meat, r.green_stuff, r.name );
}


similarly equality:

bool operator==( const Burger& l, const Burger& r ) {
  return tie( l.bacon, l.meat, l.green_stuff, name ) == tie( r.bacon, r.meat, r.green_stuff, r.name );
}

That not only saves a lot of typing but is safer too. Even better, there is no abstraction penalty to pay, a release build  produces identical code to a hand written function.

There is a caveat though, as usual care needs to be taken with floating point types. Unfortunately the ieee standard dictates that floats set to NAN will compare unordered always - even when compared with itself. Because of this float types do not satisfy Strict Weak Ordering when set to NAN, so the above tie trick fails unless you are careful to never place a NAN into a map key.


1 comment: