Efficient and simple comparison operators for structs

  • A+

The application I am working on currently has a large number structs which contain data which is input from various sources such as data bases and files. For example like this:

struct A  {     float val1;     std::string val2;     int val3;      bool operator < (const A& other) const; }; 

For processing, these structs are stored up in STL-containers, such as maps and therefore need a comparison operator. These are all the same and using simple boolean logic they can be written like this:

bool A:operator < (const A& o) const {     return val1 < o.val1 ||          (val1 == o.val1 && ( val2 < o.val2 ||              (val2 == o.val2 && ( val3 < o.val3 ) ) ); } 

This seems efficient, but has several drawbacks:

  1. These expressions get huge if the structs as a dozen or more members.
  2. It is cumbersome to write and maintain if members change.
  3. It needs to be done for every struct separately.

Is there a more maintainable way to compare structs like this?


You can use the builtin comparison that ships with <tuple> like this:

#include <tuple>  bool A::operator < (const A& rhs) const {     return std::tie(val1, val2, val3) < std::tie(rhs.val1, rhs.val2, rhs.val3); } 

This doesn't scale when more and more data members are added to the struct, but this might also be a hint that you could create intermediate structs that implement operator < and hence play well with the above implementation of a top-level operator <.

Let me add three additional comments on operator <.

  1. Once you have operator <, clients will expect that all other comparison operators are provided, too. Before we have the three-way comparison in C++20, you can avoid unnecessary boilerplate code by e.g. using the Boost operator library:

    #include <boost/operators.hpp>  struct A : private boost::totally_ordered<A> { /* ... */ }; 

    which generates all operators based on operator < and operator == for you.

  2. In your example, there is no need for the operator to be a member of A. You can make it a free function, which is preferable (see here for the rationale).

  3. If there is no intrinsic ordering related to A and you just need operator < to store instances as keys in a std::map, consider providing a named predicate.


:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: