📜 ⬆️ ⬇️

[C ++] Comparison of structures for a set of fields

Introduction


Probably, everyone faced a situation when you need to write operator == or operator <for your structure. I used to do it like this:
 struct data { unsigned int a_ ; int b_ ; int c_ ; int d_ ; } ; bool operator<(const data & a1, const data & a2) { //   a_, b_  d_ if (a1.a_ != a2.a_) return a1.a_ < a2.a_ ; if (a1.b_ != a2.b_) return a1.b_ < a2.b_ ; return a1.d_ < a2.b_ ; } 

Kopipast depressed me, but I couldn't think of anything worthwhile.

Good way


Recently there was a good way to do it:
 #include <boost/tuple/tuple_comparison.hpp> bool s(foo const &l, foo const &r) { return boost::tie(la, lb, lc, ld) < boost::tie(ra, rb, rc, rd); } 

(from here)
This method is good for almost everyone, and it was necessary to stop at it.

Crazy way


In a good way there was one minor drawback - the repetition of the list of fields. To get rid of it, it was necessary to turn to Boost.Fusion.
A set of structure fields can be represented as a set of pointers to them. For our case, it will look like this:
 &data::a_, &data::b_, &data::d_ 

It is impossible to make an array of them, because they have different types:
 unsigned int data::*, int data::*, int data::* 

Boost.Fusion has its own vector, which is a tuple of fields of different types, for example:
 vector<int, char, double> v(1, '1', 1.0) ; 

For our pointers we get the following tuple:
 vector<unsigned int data::*, int data::*, int data::*> v2(&data::a_, &data::b_, &data::d_) ; 

It looks ugly, but to create a temporary tuple, you can use the fusion::make_vector :
 make_vector(&data::a_, &data::b_, &data::d_) ; 

Hooray! Now the comparison operator can be implemented without repeating the code:
 bool operator<(const data & a1, const data & a2) { return less_members(boost::fusion::make_vector(&data::a_, &data::b_, &data::d_), a1, a2) ; } 

It remains only to figure out how to obtain from the tuple of field pointers two tuples of the field values ​​of the objects a1 and a2 . In Boost.Fusion there is a fusion::transform function, similar to std::transform , - it applies the specified transformation to each element of the tuple. The difference is that in fusion::transform the output can be a tuple of completely different types! Only the number of elements will match.

So, we need from the tuple (&data::a_, &data::b_, &data::d_) to get a tuple (a1.a_, a1.b_, a1.d_) , moreover consisting of constant links. It turns out that the conversion must take a value of type RT::* and return a value of type const R & . In addition, it must “remember” the object with which it deals, from which it acquires these values.
 template <class S> struct member_getter { member_getter(const S & obj): obj_(obj) {} template <class R> const R & operator()(RS::* pmemb) const { return obj_.*pmemb ; } const S & obj_ ; } ; 

member_getter is a functor (an object with a specific operator "parentheses"), which is constructed with the object from which to extract values ​​by the pointers to the fields. It works as follows:
 const data d = { 1U, 2, 3, 4 } ; member_getter<data> getter(d) ; const unsigned int & ra = getter(&data::a_) ; const int & rb = getter(&data::b_) ; 

But how will the fusion::transform determine the type of elements of the output tuple? For this there is a mechanism result_of , which is widely used in Boost. In order for result_of<member_getter<S>(RT::*)>::type be equal to const R & , you need to add the definition of the metafunction result to our member_getter , which will be addressed by the standard result_of implementation:
 template <class S> struct member_getter { member_getter(const S & obj): obj_(obj) {} template <class T> struct result ; template <class R> struct result<member_getter(RS::*)> { typedef const R & type ; } ; template <class R> struct result<member_getter(RS::* const &)> { typedef const R & type ; } ; template <class R> const R & operator()(RS::* pmemb) const { return obj_.*pmemb ; } const S & obj_ ; } ; 

With this definition of result , only work with pointers to the member is supported. If you try to use member_getter to convert an element of some other type, a compilation error will occur.
')
The result is the simplest implementation of less_members :
 template <class T, class S> bool less_members(const T & membs, const S & a1, const S & a2) { using boost::fusion::transform ; return transform(membs, member_getter<S>(a1)) < transform(membs, member_getter<S>(a2)) ; } 

The main comparison operators have already been defined for the tuples in Boost.Fusion.

Conclusion


In principle, if you need to compare a large number of structures on a set of fields, the above can be useful. For me, it was just an exercise and a reason to deal with result_of and fusion::transform . In normal life, the way with boost::tie more suitable, since it has less overhead and it is simpler.

Source: https://habr.com/ru/post/89874/


All Articles