No se encontró el operador == al comparar estructuras en C ++

96

Al comparar dos instancias de la siguiente estructura, recibo un error:

struct MyStruct1 {
    MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int = -1) :
        my_struct_2(_my_struct_2),
        an_int(_an_int)
    {}

    std::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

El error es:

error C2678: binario '==': no ​​se encontró un operador que toma un operando de la izquierda del tipo 'myproj :: MyStruct1' (o no hay conversión aceptable)

¿Por qué?

Jonathan
fuente

Respuestas:

126

En C ++, los structs no tienen un operador de comparación generado por defecto. Necesitas escribir el tuyo propio:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}
Anthony Williams
fuente
21
@Jonathan: ¿Por qué C ++ sabría cómo quieres comparar tus structs para la igualdad? Y si desea la forma más sencilla, siempre hay memcmptanto tiempo que sus estructuras no contienen puntero.
Xeo
12
@Xeo: memcmpfalla con miembros que no son POD (como std::string) y estructuras acolchadas.
fredoverflow
16
@Jonathan Los lenguajes "modernos" que conozco proporcionan un ==operador --- con una semántica que casi nunca es la deseada. (Y no proporcionan un medio para anularlo, por lo que termina teniendo que usar una función miembro). Los lenguajes "modernos" que conozco tampoco proporcionan semántica de valor, por lo que estás obligado a usar punteros, incluso cuando no son apropiados.
James Kanze
4
@Jonathan Los casos definitivamente varían, incluso dentro de un programa determinado. Para los objetos de entidad, la solución proporcionada por Java funciona muy bien (y, por supuesto, puede hacer exactamente lo mismo en C ++, incluso es C ++ idiomático para los objetos de entidad). La pregunta es qué hacer con los objetos de valor. C ++ proporciona un valor predeterminado operator=(incluso si a menudo hace lo incorrecto), por razones de compatibilidad con C. operator==Sin embargo, la compatibilidad con C no requiere un . Globalmente, prefiero lo que hace C ++ a lo que hace Java. (No sé C #, así que tal vez sea mejor.)
James Kanze
9
¡Al menos debería ser posible = default!
user362515
93

C ++ 20 introdujo comparaciones predeterminadas, también conocidas como "nave espacial"operator<=> , que le permite solicitar operadores </ <=/ ==/ !=/ >=/ y / o >operadores generados por el compilador con la implementación obvia / ingenua (?) ...

auto operator<=>(const MyClass&) const = default;

... pero puede personalizarlo para situaciones más complicadas (discutidas a continuación). Vea aquí la propuesta de lenguaje, que contiene justificaciones y discusión. Esta respuesta sigue siendo relevante para C ++ 17 y versiones anteriores, y para obtener información sobre cuándo debe personalizar la implementación de operator<=>....

Puede parecer un poco inútil que C ++ no haya estandarizado esto antes, pero a menudo las estructuras / clases tienen algunos miembros de datos para excluir de la comparación (por ejemplo, contadores, resultados almacenados en caché, capacidad del contenedor, código de éxito / error de la última operación, cursores), como así como decisiones a tomar acerca de innumerables cosas que incluyen, entre otras:

  • qué campos comparar primero, por ejemplo, comparar un intmiembro en particular podría eliminar el 99% de los objetos desiguales muy rápidamente, mientras que un map<string,string>miembro a menudo puede tener entradas idénticas y ser relativamente costoso de comparar - si los valores se cargan en tiempo de ejecución, el programador puede tener información sobre el el compilador no puede posiblemente
  • al comparar cadenas: sensibilidad a mayúsculas y minúsculas, equivalencia de espacios en blanco y separadores, convenciones de escape ...
  • precisión al comparar flotadores / dobles
  • si los valores de coma flotante de NaN deben considerarse iguales
  • comparando punteros o apuntados a datos (y si es el último, cómo saber si los punteros son a matrices y cuántos objetos / bytes necesitan comparación)
  • si el orden importa cuando se comparan contenedores sin clasificar (por ejemplo vector, list), y si es así, si está bien ordenarlos en el lugar antes de comparar o usar memoria adicional para clasificar los temporales cada vez que se realiza una comparación
  • cuántos elementos de la matriz contienen actualmente valores válidos que deben compararse (¿hay un tamaño en alguna parte o un centinela?)
  • que miembro de uniona comparar
  • normalización: por ejemplo, los tipos de fecha pueden permitir un día del mes o un mes del año fuera de rango, o un objeto racional / fracción puede tener 6/8 mientras que otro tiene 3/4, que por razones de rendimiento corrigen perezosamente con un paso de normalización separado; es posible que deba decidir si desencadenar una normalización antes de la comparación
  • qué hacer cuando los punteros débiles no son válidos
  • cómo manejar miembros y bases que no se implementan por operator==sí mismos (pero pueden tener compare()o operator<o str()o captadores ...)
  • qué bloqueos se deben tomar al leer / comparar datos que otros hilos pueden querer actualizar

Por lo tanto, es bueno tener un error hasta que haya pensado explícitamente en lo que debería significar la comparación para su estructura específica, en lugar de dejar que se compile pero no le dé un resultado significativo en tiempo de ejecución .

Dicho todo esto, sería bueno si C ++ le permitiera decir bool operator==() const = default;cuándo decidió que una ==prueba "ingenua" miembro por miembro estaba bien. Lo mismo para !=. Dadas las múltiples miembros / bases, "por defecto" <, <=, >, y >=las implementaciones parecen sin esperanza, aunque - en cascada sobre la base de la orden de que todo es posible, pero muy poco probable que sea lo que quería, dado conflictivos imperativos para el pedido miembro (bases de ser necesariamente ante los miembros, la agrupación por declaración accesibilidad, construcción / destrucción antes del uso dependiente). Para ser más útil, C ++ necesitaría un nuevo miembro de datos / sistema de anotación de base para guiar las elecciones; sin embargo, eso sería una gran cosa para tener en el Estándar, idealmente junto con la generación de código definido por el usuario basada en AST ... eso'

Implementación típica de operadores de igualdad

Una implementación plausible

Es probable que una implementación razonable y eficiente sea:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.my_struct2 == rhs.my_struct2 &&
           lhs.an_int     == rhs.an_int;
}

Tenga en cuenta que esto necesita una operator==para MyStruct2también.

Las implicaciones de esta implementación y las alternativas se discuten bajo el título Discusión de los detalles de su MyStruct1 a continuación.

Un enfoque coherente para ==, <,> <= etc.

Es fácil aprovechar std::tuplelos operadores de comparación para comparar sus propias instancias de clase; solo utilícelos std::tiepara crear tuplas de referencias a campos en el orden de comparación deseado. Generalizando mi ejemplo desde aquí :

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) ==
           std::tie(rhs.my_struct2, rhs.an_int);
}

inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) <
           std::tie(rhs.my_struct2, rhs.an_int);
}

// ...etc...

Cuando "posee" (es decir, puede editar, un factor con las bibliotecas corporativas y de terceros) la clase que desea comparar, y especialmente con la preparación de C ++ 14 para deducir el tipo de retorno de función de la returndeclaración, a menudo es mejor agregar un " empate "función miembro a la clase que desea poder comparar:

auto tie() const { return std::tie(my_struct1, an_int); }

Entonces las comparaciones anteriores se simplifican a:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.tie() == rhs.tie();
}

Si desea un conjunto más completo de operadores de comparación, sugiero aumentar los operadores (buscar less_than_comparable). Si no es adecuado por alguna razón, es posible que le guste o no la idea de macros de soporte (en línea) :

#define TIED_OP(STRUCT, OP, GET_FIELDS) \
    inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
    { \
        return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
    }

#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
    TIED_OP(STRUCT, ==, GET_FIELDS) \
    TIED_OP(STRUCT, !=, GET_FIELDS) \
    TIED_OP(STRUCT, <, GET_FIELDS) \
    TIED_OP(STRUCT, <=, GET_FIELDS) \
    TIED_OP(STRUCT, >=, GET_FIELDS) \
    TIED_OP(STRUCT, >, GET_FIELDS)

... que luego se puede usar a la ...

#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)

(Versión de enlace de miembros de C ++ 14 aquí )

Discusión de los detalles de su MyStruct1

Hay implicaciones en la elección de proporcionar un miembro independiente versus un miembro operator==()...

Implementación independiente

Tienes una decisión interesante que tomar. Como su clase se puede construir implícitamente a partir de a MyStruct2, una función independiente / no miembro bool operator==(const MyStruct2& lhs, const MyStruct2& rhs)admitiría ...

my_MyStruct2 == my_MyStruct1

... primero creando un temporal MyStruct1desde my_myStruct2, luego haciendo la comparación. Esto definitivamente dejaría MyStruct1::an_intestablecido el valor del parámetro predeterminado del constructor de -1. Dependiendo de si se incluyen an_intcomparación en la implementación de su operator==, una MyStruct1podría o no podría comparar igual a una MyStruct2que sí se compara igual a la MyStruct1's my_struct_2miembro! Además, la creación de un temporal MyStruct1puede ser una operación muy ineficaz, ya que implica copiar el my_struct2miembro existente en un temporal, solo para desecharlo después de la comparación. (Por supuesto, puede evitar esta construcción implícita de MyStruct1s para la comparación haciendo ese constructor explicito eliminando el valor predeterminado para an_int).

Implementación de miembros

Si desea evitar la construcción implícita de a MyStruct1desde a MyStruct2, convierta el operador de comparación en una función miembro:

struct MyStruct1
{
    ...
    bool operator==(const MyStruct1& rhs) const
    {
        return tie() == rhs.tie(); // or another approach as above
    }
};

Tenga en cuenta que la constpalabra clave, solo necesaria para la implementación del miembro, le advierte al compilador que comparar objetos no los modifica, por lo que se puede permitir en constobjetos.

Comparando las representaciones visibles

A veces, la forma más fácil de obtener el tipo de comparación que desea puede ser ...

    return lhs.to_string() == rhs.to_string();

... que a menudo también es muy caro, ¡esos que se stringcrean dolorosamente solo para tirarlos! Para los tipos con valores de punto flotante, comparar representaciones visibles significa que el número de dígitos mostrados determina la tolerancia dentro de la cual los valores casi iguales se tratan como iguales durante la comparación.

Tony Delroy
fuente
Bueno, en realidad para los operadores de comparación <,>, <=,> = solo debería ser necesario implementar <. El resto sigue, y no hay una forma significativa de implementarlos, lo que significa algo diferente a la implementación que se puede generar automáticamente. Es extraño que tengas que implementarlos todos tú mismo.
André
@ André: más a menudo un escrito manualmente int cmp(x, y)o comparefunción que devuelve un valor negativo para x < y, 0 por la igualdad y un valor positivo para x > yse utiliza como base para <, >, <=, >=, ==, y !=; es muy fácil usar el CRTP para inyectar todos esos operadores en una clase. Estoy seguro de que publiqué la implementación en una respuesta anterior, pero no pude encontrarla rápidamente.
Tony Delroy
@TonyD Claro que se puede hacer eso, pero es tan fácil de implementar >, <=y >=en cuanto a <. También podría implementar ==y de !=esa manera, pero supongo que normalmente no sería una implementación muy eficiente. Sería bueno si no se necesitaran CRTP u otros trucos para todo esto, pero el estándar solo exigiría la generación automática de estos operadores si el usuario no los define explícitamente y <está definido.
André
@ André: es porque ==y !=puede que no se exprese de manera eficiente usando <que usar comparar para todo es común. "Sería bueno si no se necesitaría CRTP u otros trucos" - tal vez, pero luego CRTP puede ser fácilmente utilizado para generar gran cantidad de otros operadores (por ejemplo, bit a bit |, &, ^de |=, &=y ^=, + - * / %de sus formas de asignación; binaria -de la negación unaria y +) - tantas variaciones potencialmente útiles sobre este tema que solo proporcionar una función de lenguaje para una porción bastante arbitraria de eso no es particularmente elegante.
Tony Delroy
¿Le importaría agregar a una implementación plausible una versión que se utiliza std::tiepara hacer la comparación de varios miembros?
NathanOliver
17

Necesita definir explícitamente operator ==para MyStruct1.

struct MyStruct1 {
  bool operator == (const MyStruct1 &rhs) const
  { /* your logic for comparision between "*this" and "rhs" */ }
};

Ahora la comparación == es legal para 2 de estos objetos.

iammilind
fuente
11

A partir de C ++ 20, debería ser posible añadir un conjunto completo de operadores de comparación predeterminado ( ==, <=, etc.) a una clase declarando un operador de comparación de tres vías por defecto ( "nave espacial" operador), así:

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

Con un compilador compatible con C ++ 20, agregar esa línea a MyStruct1 y MyStruct2 puede ser suficiente para permitir comparaciones de igualdad, asumiendo que la definición de MyStruct2 es compatible.

Joe Lee
fuente
2

La comparación no funciona en estructuras en C o C ++. En su lugar, compare por campos.

Rafe Kettler
fuente
2

Por defecto, las estructuras no tienen ==operador. Tendrás que escribir tu propia implementación:

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }
Jonathan
fuente
0

Fuera de la caja, el operador == solo funciona para primitivas. Para que su código funcione, necesita sobrecargar el operador == para su estructura.

Babak Naffas
fuente
0

Porque no escribió un operador de comparación para su estructura. El compilador no lo genera por usted, por lo que si desea una comparación, debe escribirlo usted mismo.


fuente