简体   繁体   中英

Is there a wrapper for floating point numbers in C++20 that would enable me to default the spaceship operator?

I was watching "Using C++20 three way comparison - Jonathan Müller - Meeting C++ 2019" talk and it mentioned problems with classes that contain floating point members.

Problem comes from the fact that IEEE 754 comparisons involving NaN(s) are weird and do not provide total ordering. Talk gives a way to work around this problems, for example using strong_order or manually ignoring NaN values when implementing <=> (assuming that values are never NaN).

My questions is if there are some library wrappers that would enable me to say that "I promise" that my floats are never NaN or that would do slow but valid comparisons on floats(slower but safer since NaNs are now ordered). My goal is to avoid manual implementation of spaceship by making member float spaceship friendly(so I can default spaceship).

Using example from the talk:

// original class
struct Temperature{
    double value;
};

struct TemperatureNoNan{
    std::a_number<double> value; // I promise value will never be NaN
    // Now spaceship defaulting works
};

struct TemperatureStrongO{
    std::s_ordered<double> value; // I want strong ordering(2 diff NaNs are not the same)
    // Now spaceship defaulting works
};

"I promise" that my floats are never NaN

template <std::floating_point T>
struct NeverNaN {
    T val;
    constexpr NeverNaN(T val) : val(val) { }
    constexpr operator T() const { return val; }

    constexpr bool operator==(NeverNaN const&) const = default;

    constexpr std::strong_ordering operator<=>(NeverNaN const& rhs) const {
        auto c = val <=> rhs.val;
        assert(c != std::partial_ordering::unordered);
        return c > 0 ? std::strong_ordering::greater :
                c < 0 ? std::strong_ordering::less :
                std::strong_ordering::equal;
    }
};

Unfortunately, there's no good way to "lift" a comparison category like this. And it doesn't optimize very well at the moment.

that would do slow but valid comparisons on floats(slower but safer since NaNs are now ordered)

This one has specific library support by way of either std::strong_order() or std::weak_order() [cmp.alg] depending on what kind of comparison you want:

template <std::floating_point T>
struct TotallyOrdered {
    T val;
    constexpr TotallyOrdered(T val) : val(val) { }
    constexpr operator T() const { return val; }

    // depends on whether or not you want == NaN to still be false?
    // might need to be: return (*this <=> rhs) == 0; 
    constexpr bool operator==(TotallyOrdered const&) const = default;

    constexpr auto operator<=>(TotallyOrdered const& rhs) const {
        return std::strong_order(val, rhs.val);
        // ... or std::weak_order(val, rhs.val)
    }
};

There's nothing in the standard library for this, but it's trivial to implement:

template<class T,class O=std::weak_ordering>
struct a_number {
  T t;
  O operator<=>(const a_number &rhs) const {
    return t<rhs.t ? O::less : t==rhs.t ? O::equivalent : O::greater;
  }
};

template<class T>
struct s_ordered {
  T t;
  auto operator<=>(const s_number &rhs) const {
    return std::strong_order(t,rhs.t);
  }
};

…with whatever other conversion operators or other conveniences desired.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM