El operador de igualdad no se define para una implementación de operador de nave espacial personalizada en C ++ 20

51

Me encuentro con un comportamiento extraño con el nuevo operador de nave espacial <=>en C ++ 20. Estoy usando el compilador de Visual Studio 2019 con /std:c++latest.

Este código se compila bien, como se esperaba:

#include <compare>

struct X
{
    int Dummy = 0;
    auto operator<=>(const X&) const = default; // Default implementation
};

int main()
{
    X a, b;

    a == b; // OK!

    return 0;
}

Sin embargo, si cambio X a esto:

struct X
{
    int Dummy = 0;
    auto operator<=>(const X& other) const
    {
        return Dummy <=> other.Dummy;
    }
};

Me sale el siguiente error del compilador:

error C2676: binary '==': 'X' does not define this operator or a conversion to a type acceptable to the predefined operator

Intenté esto también en clang, y tengo un comportamiento similar.

Agradecería alguna explicación sobre por qué la implementación predeterminada se genera operator==correctamente, pero la personalizada no.

Zeenobit
fuente

Respuestas:

50

Esto es por diseño.

[class.compare.default] (énfasis mío)

3 Si la definición de clase no declara explícitamente una == función de operador, pero declara una función de operador de comparación de tres vías predeterminada , una ==función de operador se declara implícitamente con el mismo acceso que la función de operador de comparación de tres vías. El ==operador declarado implícitamente para una clase X es un miembro en línea y se define como predeterminado en la definición de X.

Solo un valor predeterminado <=>permite ==que exista un sintetizado . La razón es que las clases como std::vectorno pueden usar un valor predeterminado <=>. Además, usar <=>for ==no es la forma más eficiente de comparar vectores. <=>debe dar el orden exacto, mientras que ==puede rescatar antes comparando primero los tamaños.

Si una clase hace algo especial en su comparación tripartita, es probable que deba hacer algo especial en ella ==. Por lo tanto, en lugar de generar un valor predeterminado no sensible, el lenguaje lo deja en manos del programador.

StoryTeller - Unslander Monica
fuente
44
Ciertamente es sensato, a menos que la nave espacial tenga errores. Aunque potencialmente extremadamente ineficiente ...
Deduplicador
1
@Dupuplicator: la sensibilidad es subjetiva. Algunos dirían que una implementación ineficiente generada silenciosamente no es sensata.
StoryTeller - Unslander Monica
45

Durante la estandarización de esta característica, se decidió que la igualdad y el orden deberían estar separados lógicamente. Como tal, los usos de las pruebas de igualdad ( ==y !=) nunca invocarán operator<=>. Sin embargo, todavía se consideró útil poder predeterminarlos con una sola declaración. Por lo tanto, si establece un valor predeterminado operator<=>, se decidió que también tenía la intención de hacerlo operator==(a menos que lo defina más tarde o lo haya definido antes).

En cuanto a por qué se tomó esta decisión , el razonamiento básico es el siguiente. Considere std::string. El pedido de dos cadenas es lexicográfico; cada carácter tiene su valor entero comparado con cada carácter en la otra cadena. La primera desigualdad resulta en el resultado de ordenar.

Sin embargo, la prueba de igualdad de cadenas tiene un cortocircuito. Si las dos cadenas no tienen la misma longitud, entonces no tiene sentido hacer una comparación entre caracteres; No son iguales. Entonces, si alguien está haciendo pruebas de igualdad, no querrá hacerlo de forma larga si puede hacer un corto circuito.

Resulta que muchos tipos que necesitan un pedido definido por el usuario también ofrecerán algún mecanismo de cortocircuito para las pruebas de igualdad. Para evitar que las personas solo implementen operator<=>y desechen el rendimiento potencial, obligamos a todos a hacer ambas cosas.

Nicol Bolas
fuente
55
Esta es una explicación mucho mejor que la respuesta aceptada
memo
17

Las otras respuestas explican muy bien por qué el lenguaje es así. Solo quería agregar que en caso de que no sea obvio, por supuesto, es posible que un usuario proporcione operator<=>un valor predeterminado operator==. Solo necesita escribir explícitamente el valor predeterminado operator==:

struct X
{
    int Dummy = 0;
    auto operator<=>(const X& other) const
    {
        return Dummy <=> other.Dummy;
    }
    bool operator==(const X& other) const = default;
};
Oktalist
fuente