Saltar al contenido

¿Cómo comparar estructuras genéricas en C ++?

Recabamos en todo el mundo on line para traerte la solución para tu problema, si tienes alguna difcultad puedes dejarnos la inquietud y te respondemos porque estamos para ayudarte.

Solución:

Tiene razón en que el relleno se interpone en su manera de comparar tipos arbitrarios de esta manera.

Hay medidas que puede tomar:

  • Si tienes el control de Data entonces, por ejemplo, gcc tiene __attribute__((packed)). Tiene un impacto en el rendimiento, pero valdría la pena intentarlo. Sin embargo, tengo que admitir que no sé si packed le permite rechazar el relleno por completo. Gcc doc dice:

Esta attribute, adjunto a la definición de tipo de estructura o unión, especifica que cada miembro de la estructura o unión se coloca para minimizar la memoria requerida. Cuando se adjunta a una definición de enumeración, indica que se debe usar el tipo integral más pequeño.

  • Si no tienes el control de Data entonces al menos std::has_unique_object_representations puede decirle si su comparación arrojará resultados correctos:

Si T es TriviallyCopiable y si dos objetos de tipo T con el mismo valor tienen la misma representación de objeto, proporciona el valor constante del miembro igual true. Para cualquier otro tipo, el valor es false.

y además:

Este rasgo se introdujo para que sea posible determinar si un tipo puede ser correctamente hash mediante el hash de su representación de objeto como un byte. array.

PD: solo abordé el relleno, pero no olvide que los tipos que pueden comparar iguales para instancias con diferente representación en la memoria no son de ninguna manera raros (p. Ej. std::string, std::vector y muchos otros).

No, memcmp no es adecuado para hacer esto. Y la reflexión en C ++ es insuficiente para hacer esto en este punto (habrá compiladores experimentales que admitan la reflexión lo suficientemente fuerte como para hacer esto ya, y C ++ 23 podría tener las características que necesita).

Sin la reflexión incorporada, la forma más fácil de resolver su problema es hacer una reflexión manual.

Toma esto:

struct some_struct 
  int x;
  double d1, d2;
  char c;
;

queremos hacer la mínima cantidad de trabajo para poder comparar dos de estos.

Si tenemos:

auto as_tie(some_struct const& s) 
  return std::tie( s.x, s.d1, s.d2, s.c );

o

auto as_tie(some_struct const& s)
-> decltype(std::tie( s.x, s.d1, s.d2, s.c ))

  return std::tie( s.x, s.d1, s.d2, s.c );

para c ++ 11, entonces:

template
bool are_equal( S const& lhs, S const& rhs ) 
  return as_tie(lhs) == as_tie(rhs);

hace un trabajo bastante decente.

Podemos extender este proceso para que sea recursivo con un poco de trabajo; en lugar de comparar vínculos, compare cada elemento envuelto en una plantilla, y esa plantilla operator== aplica de forma recursiva esta regla (envolviendo el elemento en as_tie para comparar) a menos que el elemento ya tenga un trabajo ==y maneja matrices.

Esto requerirá un poco de biblioteca (¿100 líneas de código?) Junto con escribir un poco de datos de “reflexión” manual por miembro. Si el número de estructuras que tiene es limitado, podría ser más fácil escribir código por estructura manualmente.


Probablemente hay formas de conseguir

REFLECT( some_struct, x, d1, d2, c )

para generar el as_tie estructura usando macros horribles. Pero as_tie es bastante simple. En c ++ 11 la repetición es molesta; esto es útil:

#define RETURNS(...) 
  noexcept(noexcept(__VA_ARGS__)) 
  -> decltype(__VA_ARGS__) 
   return __VA_ARGS__; 

en esta situación y muchas otras. Con RETURNS, escribiendo as_tie es:

auto as_tie(some_struct const& s)
  RETURNS( std::tie( s.x, s.d1, s.d2, s.c ) )

quitando la repetición.


Aquí hay una puñalada para hacerlo recursivo:

template, bool>::type = true
>
auto refl_tie( T const& t )
  RETURNS(std::tie(t))

template 1), bool>::type = true
>
auto refl_tie( Ts const&... ts )
  RETURNS(std::make_tuple(refl_tie(ts)...))

template
auto refl_tie( T const(&t)[N] ) 
  // lots of work in C++11 to support this case, todo.
  // in C++17 I could just make a tie of each of the N elements of the array?

  // in C++11 I might write a custom struct that supports an array
  // reference/pointer of fixed size and implements =, ==, !=, <, etc.


struct foo 
  int x;
;
struct bar 
  foo f1, f2;
;
auto refl_tie( foo const& s )
  RETURNS( refl_tie( s.x ) )
auto refl_tie( bar const& s )
  RETURNS( refl_tie( s.f1, s.f2 ) )

c ++ 17 refl_tie (array) (totalmente recursivo, incluso admite matrices de matrices):

template
auto array_refl( T const(&t)[N], std::index_sequence )
  RETURNS( std::array refl_tie( t[Is] )...  )

template
auto refl_tie( T(&t)[N] )
  RETURNS( array_refl( t, std::make_index_sequence ) )

Ejemplo vivo.

Aquí uso un std::array de refl_tie. Esto es mucho más rápido que mi tupla anterior de refl_tie en tiempo de compilación.

También

template, bool>::type = true
>
auto refl_tie( T const& t )
  RETURNS(std::cref(t))

utilizando std::cref aquí en lugar de std::tie podría ahorrar en tiempo de compilación, ya que cref es una clase mucho más simple que tuple.

Finalmente, debes agregar

template
auto refl_tie( T(&t)[N], Ts&&... ) = delete;

que evitará array que los miembros decaigan a punteros y recurran a la igualdad de punteros (que probablemente no desee de las matrices).

Sin esto, si pasa un array a una estructura no reflejada en, recurre a la estructura de puntero a no reflejada refl_tie, que funciona y devuelve tonterías.

Con esto, terminas con un error en tiempo de compilación.


El soporte para la recursividad a través de tipos de biblioteca es complicado. Tú podrías std::tie ellos:

template
auto refl_tie( std::vector const& v )
  RETURNS( std::tie(v) )

pero eso no admite la recursividad a través de él.

En resumen: no es posible de forma genérica.

El problema con memcmp es que el relleno puede contener datos arbitrarios y, por lo tanto, el memcmp Puede fallar. Si hubiera una manera de averiguar dónde está el relleno, podría poner a cero esos bits y luego comparar las representaciones de datos, eso verificaría la igualdad si los miembros son trivialmente comparables (lo cual no es el caso, es decir, para std::string ya que dos cadenas pueden contener punteros diferentes, pero las dos matrices de caracteres en punta son iguales). Pero no conozco ninguna forma de llegar al relleno de estructuras. Puede intentar decirle a su compilador que empaque las estructuras, pero esto hará que los accesos sean más lentos y no está realmente garantizado que funcione.

La forma más limpia de implementar esto es comparar todos los miembros. Por supuesto, esto no es realmente posible de una manera genérica (hasta que obtengamos reflexiones de tiempo de compilación y metaclases en C ++ 23 o posterior). Desde C ++ 20 en adelante, se podría generar un valor predeterminado operator<=> pero creo que esto solo sería posible como una función miembro, así que, nuevamente, esto no es realmente aplicable. Si tiene suerte y todas las estructuras que desea comparar tienen un operator== definido, por supuesto, puede usarlo. Pero eso no está garantizado.

EDITAR: De acuerdo, en realidad hay una forma totalmente hacky y algo genérica para los agregados. (Solo escribí la conversión a tuplas, esas tienen un operador de comparación predeterminado). Godbolt

Si guardas alguna indecisión o forma de perfeccionar nuestro post eres capaz de añadir una nota y con mucho gusto lo interpretaremos.

¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *