Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Efficient and simple comparison operators for structs

Tags:

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?

like image 769
Beginner Avatar asked Sep 05 '18 07:09

Beginner


People also ask

Can you compare structs in C++?

Comparison doesn't work on structs in C or C++. Compare by fields instead. Show activity on this post.

What are the six comparison operators?

A comparison operator compares two values and returns a boolean value, either True or False . Python has six comparison operators: less than ( < ), less than or equal to ( <= ), greater than ( > ), greater than or equal to ( >= ), equal to ( == ), and not equal to ( != ).

Which operators are used for comparing values?

The equality operator (==) is used to compare two values or expressions. It is used to compare numbers, strings, Boolean values, variables, objects, arrays, or functions. The result is TRUE if the expressions are equal and FALSE otherwise.


2 Answers

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.

like image 60
lubgr Avatar answered Sep 30 '22 20:09

lubgr


Great answer by lubgr.

One further refinement I perform is the creation of a member function as_tuple on any object which is to be ordered by its members:

#include <string>
#include <tuple>
#include <iostream>

struct A 
{
    float val1;
    std::string val2;
    int val3;

    // provide easy conversion to tuple
    auto as_tuple() const
    {
        return std::tie(val1, val2, val3);
    }
};

Which often gives rise to thoughts of a general system of making objects and tuples interchangeable in terms of comparisons

template<class T> auto as_tuple(T&& l) -> decltype(l.as_tuple()) 
{
    return l.as_tuple();
}

template<class...Ts> 
auto as_tuple(std::tuple<Ts...> const& tup) 
-> decltype(auto)
{
    return tup;
}

template<class L, class R>
auto operator < (L const& l, R const& r)
-> decltype(as_tuple(l), void(), as_tuple(r), void(), bool())
{
    return as_tuple(l) < as_tuple(r);
}

Which allows such code as:

int main()
{
    auto a = A { 1.1, "foo", 0 };
    auto b = A { 1.1, "foo", 1 };

    auto test1 = a < b;
    std::cout << test1 << std::endl;

    auto test2 = a < std::make_tuple(1.1, "bar", 0);
    std::cout << test2 << std::endl;

    auto test3 = std::make_tuple(1.0, "bar", 0) < std::make_tuple(1.1, "bar", 0);
    std::cout << test3 << std::endl;

    auto test4 = a < std::make_tuple(2l, std::string("bar"), 0);
    std::cout << test4 << std::endl;

}

example: http://coliru.stacked-crooked.com/a/ead750f3f65e3ee9

like image 30
Richard Hodges Avatar answered Sep 30 '22 19:09

Richard Hodges