Reputation: 5477
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?
Upvotes: 20
Views: 2309
Reputation: 69942
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
Upvotes: 9
Reputation: 38325
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.
Upvotes: 25