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:
Is there a more maintainable way to compare structs like this?
Comparison doesn't work on structs in C or C++. Compare by fields instead. Show activity on this post.
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 ( != ).
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.
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 <
.
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.
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).
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With