C ++ automatic comparator for structures

There are many primitive structures (several hundred) that are used to transfer data between two components (for example, a player and a server). They don't have methods, just raw data. The challenge is to write all the requests and responses to be able to replay the player's scenario without a server (we remember all the questions and all the answers, which are pure functions). Thus, the task is put on this structure on the map without a comparator. Now we use memcmp, it allows us not to think about changes in these structures and is compact, but there are too many problems with filling, etc.

Is it possible to get smth as getHashValue or any default comparator with metaprogramming in C ++? conditions: 1) I don't want to create a comparator for each structure. 2) I want to have an error if a field has been added or removed, if it breaks existing behavior and needs fixing. 3) I don't want to change header files with structure definitions.

Example structure

struct A {
  int a;
  int b;
  c c;
}

bool operator<(const A& a1, const A& a2)
{
  if (a1.a != a2.a) return a1.a < a2.a;
  if (a1.b != a2.b) return a1.b < a2.b;
  if (a1.c != a2.c) return a1.c < a2.c;
  return false;
} 

      

I can consider other languages ​​to implement this exact part (collect questions / answers), if it doesn't require describing all these structures in that language again.

+3


source to share


3 answers


In C ++ 17, you can turn this off if you want to (A) hardcode how many elements are in each structure somewhere, and (B) write or generate code for each count of the number of elements in the structure.

template<std::size_t N>
using size_k = std::integral_constant<std::size_t, N>;

template<class T>
auto auto_tie( size_k<0>, T&& t ) {
  return std::tie();
}
template<class T>
auto auto_tie( size_k<1>, T&& t ) {
  auto& [ x0 ] = std::forward<T>(t);
  return std::tie( x0 );
}
template<class T>
auto auto_tie( size_k<2>, T&& t ) {
  auto& [ x0, x1 ] = std::forward<T>(t);
  return std::tie( x0, x1 );
}
// etc

      

now, in the namespace of the structure in question, write

struct foo {
  int x;
};
struct bar {
  int a, b;
};
size_k<1> elems( foo const& ) { return {}; }
size_k<2> elems( bar const& ) { return {}; }

      

a elems

, returning the size_k

count of the number of elements.

Now in the structs namespace write:

template<class T, class Size=decltype(elems(std::declval<T const&>()))>
bool operator<( T const& lhs, T const& rhs ) {
  return auto_tie( Size{}, lhs ) < auto_tie( Size{}, rhs );
}

      



and you're done.

Test code:

foo f0{1}, f1{2};
bar b0{1,2}, b1{-7, -3};

std::cout << (f0<f1) << (f1<f0) << (f0<f0) << "\n";
std::cout << (b0<b1) << (b1<b0) << (b0<b0) << "\n";

      

A living example .

This would require writing third party tools or waiting for the display extension in C ++, perhaps C ++ 20 or 23.

If you're wrong elems

, I believe the structured bindings code auto_tie

should generate an error.

+2


source


I suppose you could write your own memcmp

. Based comparison operator .

bool operator<(const A &lhs, const A &rhs) {
    return memcmp(&lhs, &rhs, sizeof(A)) < 0;
}

      

Of course, writing for each object can be a burden, so you can write a template for that. Though without SFINAE, it would cover too many types.



 #include <type_traits>
 #include <cstring>

 template<typename T>
 std::enable_if_t<std::is_pod_v<std::decay_t<T>      //< Check if POD
                  && !std::is_fundamental_v<std::decay_t<T>>>, //< Don't override for fundamental types like 'int'
     bool>
     operator<(const T &lhs, const T &rhs) {
     return memcmp(&lhs, &rhs, sizeof(std::decay_t<T>)) < 0;
 }

      

EDIT: Please note that for this technique you need to initialize the structures with zero initialization.

+3


source


It looks like the best way to do this is to write a generator that will generate .h and .cpp using the bool <operator for all types in that header file. Then add this project as a preliminary step to the main one.

It doesn't seem like a nice solution, but it avoids manual handling of code duplication and will support adding / removing new structures / fields. So I haven't found a better way.

0


source







All Articles