19

随着operator<=>被添加到 C++20 中,我想尝试推理如何在那些不是简单的成员比较的情况下实现这个运算符。

您将如何实现用于比较 anoptional<T>与 anoptional<U>或 a的 spaceship 运算符U,在这种情况下,我们要么必须将 theT与 the进行比较,U要么比较底层状态,以获得正确的返回类型?最新论文中没有这样的例子。

4

1 回答 1

11

<=>我们必须做出决定:如果我们需要的底层比较还没有实现,我们想做什么<=>

一种选择是:不在乎。只要使用<=>,如果相关类型不提供,我们没有可比性。这使得实现非常简洁(请注意,您需要对==总共六个函数执行相同的操作):

template <typename T>
class optional {
public:
    // ...

    template <typename U>
    constexpr std::compare_three_way_result_t<T, U>
    operator<=>(optional<U> const& rhs) const
    {
        if (has_value() && rhs) {
            return **this <=> *rhs;
        } else {
            return has_value() <=> rhs.has_value();
        }
    }

    template <typename U>
    constexpr std::compare_three_way_result_t<T, U>
    operator<=>(U const& rhs) const
    {
        if (has_value()) {
            return **this <=> *rhs;
        } else {
            return strong_ordering::less;
        }
    }

    constexpr strong_ordering operator<=>(nullopt_t ) const {
        return has_value() ? strong_ordering::greater
                           : strong_ordering::equal;
    }
};

s之间的三向比较bool产生std::strong_ordering,它可以隐式转换为其他比较类别。同样,strong_ordering::less可以隐式转换为weak_ordering::less, partial_ordering::less, strong_equality::unequal, 或weak_equality::nonequivalent, 视情况而定。

以上是超级好的,快乐的答案。希望随着时间的推移,人们会采用<=>并且越来越多的代码将能够依赖于快乐的答案。


另一种选择是:我们确实关心,并希望退回到综合排序。也就是说,如果我们需要比较 aT和 aU而它们不提供 a <=>,我们可以使用标准库中的新自定义点对象之一。还是哪一个?最保守的选择是将 apartial_ordering与合成compare_partial_order_fallback。这保证了我们总能得到正确的答案。

对于optional<T>vsoptional<U>比较,看起来像:

template <typename T>
class optional {
public:
    // ...

    template <typename U>
    constexpr auto operator<=>(optional<U> const& rhs) const
        -> decltype(std::compare_partial_order_fallback(**this, *rhs))
    {
        if (has_value() && rhs) {
            return std::compare_partial_order_fallback(**this, *rhs);
        } else {
            return has_value() <=> rhs.has_value();
        }
    }

    // ...
};

不幸的是,如上所述,我们的比较现在总是返回partial_ordering——即使是在两个optional<int>s 之间。因此,更好的选择可能是使用任何<=>回报,否则使用保守的后备。我还不知道如何命名这个概念,所以我会选择:

template <typename T, std::three_way_comparable_with<T> U>
constexpr auto spaceship_or_fallback(T const& t, U const& u) {
    return t <=> u;
}

template <typename T, typename U>
constexpr auto spaceship_or_fallback(T const& t, U const& u)
    -> decltype(std::compare_partial_order_fallback(t, u))
{
    return std::compare_partial_order_fallback(t, u);
}

并使用它:

template <typename T>
class optional {
public:
    // ...

    template <typename U>
    constexpr auto operator<=>(optional<U> const& rhs) const
        -> decltype(spaceship_or_fallback(**this, *rhs))
    {
        if (has_value() && rhs) {
            return spaceship_or_fallback(**this, *rhs);
        } else {
            return has_value() <=> rhs.has_value();
        }
    }

    // ...
};

第三个选项,最保守的选项,是标准库将采用的选项。提供关系运算符向比较:

template <typename T> class optional { /* ... */ };

template <typename T, typename U>
constexpr bool operator<(optional<T> const&, optional<U> const&);
template <typename T, typename U>
constexpr bool operator>(optional<T> const&, optional<U> const&);
template <typename T, typename U>
constexpr bool operator<=(optional<T> const&, optional<U> const&);
template <typename T, typename U>
constexpr bool operator>=(optional<T> const&, optional<U> const&);

template <typename T, std::three_way_comparable_with<T> U>
std::compare_three_way_result_t<T, U>
operator<=>(optional<T> const& x, optional<U> const& y) {
    if (x && y) {
        return *x <=> *y;
    } else {
        return x.has_value() <=> y.has_value();
    }
}

这是最保守的选项,因为它有效地采用了 C++17 及更早版本的比较实现策略C++20 比较实现策略。如:“为什么不两者兼而有之?” 战略。使用conceptonoperator<=>可以确保尽可能a < b调用<=>,而不是<.

这是迄今为止最乏味和最冗长的方法,有很多样板,但它确保对于现有的单对象比较类型,现有比较继续工作并做同样的事情。标准库必须像这样保守。

但是新代码没有。

于 2017-11-15T19:17:35.523 回答