La mejor manera de evitar la duplicación de código es definir el operador de comparación "<, < = >, >, =,! =", ¿Pero considerando a Nancy?

He tenido este problema muchas veces.

I matemáticas, x <= y es equivalente a !(x > y). En la mayoría de los casos, lo mismo ocurre con las operaciones de coma flotante, pero no siempre. Cuando x o y es Nan, x <= y no es igual a !(x > y), ya que la comparación de NaN con cualquier contenido siempre devuelve false. Sin embargo, x <= y <=> !(x > y) es correcto en la mayoría de los casos.
Ahora, supongamos que estoy escribiendo una clase que contiene valores de coma flotante para los que quiero definir un operador de comparación. Para mayor claridad, supongamos que estoy escribiendo un número de coma flotante de alta precisión que almacena números de alta precisión internamente usando uno o más valores double. Matemáticamente, la definición x < y de esta clase ya define todos los demás operadores (si estoy de acuerdo con la semántica común del operador de comparación). Pero NaN vamos a romper esta precisión matemática. Por lo tanto, tal vez tenga que escribir muchos de estos operadores por separado sólo para pensar en Nan. ¿Pero hay una mejor manera? ¿Mi pregunta es: cómo evitar la duplicación de código tanto como sea posible y todavía respetar el comportamiento de NaN?
Correlación: http://www.boost.org/doc/libs/1_59_0/libs/utility/operators.htm. ¿Cómo pueden boost/Operators resolver este problema?
Nota: He marcado esta pregunta como c++ porque eso es lo que entiendo. Por favor, escriba un ejemplo en ese idioma. Personalmente, usaré una técnica similar a this answer que define funciones de comparación basadas en

Una solución para mí

, produciendo un orden estrictamente débil. Para los tipos con valores nulos, esto significa que la comparación siempre produce operator<(), la operación se define de acuerdo con false, proporcionando un orden estrictamente débil en todos los valores no nulos, y probando operator<().
Por ejemplo, el código podría ser el siguiente:
namespace nullable_relational {
    struct tag {};

    template <typename T>
    bool non_null(T const& lhs, T const& rhs) {
        return !is_null(lhs) && !is_null(rhs);
    }

    template <typename T>
    bool operator== (T const& lhs, T const& rhs) {
        return non_null(lhs, rhs) && !(rhs < lhs) && !(lhs < rhs);
    }
    template <typename T>
    bool operator!= (T const& lhs, T const& rhs) {
        return non_null(lhs, rhs) || !(lhs == rhs);
    }

    template <typename T>
    bool operator> (T const& lhs, T const& rhs) {
        return non_null(lhs, rhs) && rhs < lhs;
    }
    template <typename T>
    bool operator<= (T const& lhs, T const& rhs) {
        return non_null(lhs, rhs) && !(rhs < lhs);
    }
    template <typename T>
    bool operator>= (T const& lhs, T const& rhs) {
        return non_null(lhs, rhs) && !(lhs < rhs);
    }
}
Su uso es el siguiente:
#include <cmath>
class foo
    : private nullable_relational::tag {
    double value;
public:
    foo(double value): value(value) {}
    bool is_null() const { return std::isnan(this->value); }
    bool operator< (foo const& other) const { return this->value < other.value; }
};
bool is_null(foo const& value) { return value.is_null(); }
Una variante del mismo tema puede ser una implementación de una función de comparación que es parametrizada por la función de comparación y es responsable de proporcionar los parámetros apropiados a la función de comparación. Por ejemplo:
namespace compare_relational {
    struct tag {};

    template <typename T>
    bool operator== (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs == rhs; });
    }
    template <typename T>
    bool operator!= (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs != rhs; });
    }

    template <typename T>
    bool operator< (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs < rhs; });
    }
    template <typename T>
    bool operator> (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs > rhs; });
    }
    template <typename T>
    bool operator<= (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs <= rhs; });
    }
    template <typename T>
    bool operator>= (T const& lhs, T const& rhs) {
        return compare(lhs, rhs, [](auto&& lhs, auto&& rhs){ return lhs >= rhs; });
    }
}

class foo
    : private compare_relational::tag {
    double value;
public:
    foo(double value): value(value) {}

    template <typename Compare>
    friend bool compare(foo const& f0, foo const& f1, Compare&& predicate) {
        return predicate(f0.value, f1.value);
    }
};
Puedo imaginar múltiples de estas operaciones generando espacios de nombres para apoyar las opciones apropiadas en situaciones comunes. Otra opción puede ser un orden diferente al de los puntos flotantes, por ejemplo, tratar los valores nulos como mínimos o máximos. Debido a que algunas personas usan boxeo Nan, incluso pueden proporcionar un orden basado en diferentes valores de Nan y poner los valores de Nan en su lugar. Por ejemplo, utilice la representación de nivel inferior para indicar el orden general en el que se proporcionan los valores de coma flotante, que puede ser apropiado para utilizar objetos como claves en un contenedor ordenado, aunque el orden puede ser diferente del orden en el que se creó is_null().