【问题标题】:Implementing operator<=> for optional<T>为可选<T>实现运算符<=>
【发布时间】:2018-04-29 03:47:37
【问题描述】:

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

您将如何实现用于比较 optional&lt;T&gt;optional&lt;U&gt;U 的宇宙飞船运算符,在这种情况下,我们要么必须将 TU 进行比较,要么比较底层状态,得到正确的返回类型? latest paper中没有这样的例子。

【问题讨论】:

  • 顺便说一句,该功能与当前有效的a&gt;&gt;operator&lt;=&gt;&gt;c; 不兼容。
  • @JohannesSchaub-litb 是的。提案中也提到了这一点:“令牌化像往常一样遵循最大咀嚼。 [...] 这是唯一已知的向后源不兼容,并且是故意的。 (我们可以采用一个特殊的解析规则来保持这样的代码在没有空格的情况下工作,但我不鼓励这样做,因为它对麻烦的好处太少了。)”。无论如何,您通常都会有空格和/或括号,我想我同意这里的提议。
  • @Barry 据我所知,提案中没有提到它(它只给出了以下比较,但没有给出后续切换,因为向后不兼容。它说“这是只有已知的向后源不兼容")
  • @JohannesSchaub-litb 1. 请检查您要回复的人;巴里没有说提案提到过,我说了,我几乎错过了你的评论。 2. 我假设您的意思是“跟随转变”,因为我看不出跟随切换如何导致问题。 3. 我读到它说最大咀嚼是唯一已知的向后源不兼容,尽管我不知道为什么 Herb 没有将 shift 列为他知道发生最大咀嚼的两个地方之一。
  • (作为参考,here 是一个示例,其中最大 munch 会更改移位表达式中的标记化)

标签: c++ c++20 spaceship-operator


【解决方案1】:

对于&lt;=&gt;,我们必须做出决定:如果我们需要的底层比较还没有实现&lt;=&gt;,我们想做什么?

一个选项是:不在乎。只需使用&lt;=&gt;,如果相关类型不提供,我们就没有可比性。这使得实现非常简洁(请注意,您需要为== 做同样的事情,总共六个函数):

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;
    }
};

bools 之间的三路比较产生std::strong_ordering,它可以隐式转换为其他比较类别。同样,strong_ordering::less 可以隐式转换为 weak_ordering::lesspartial_ordering::lessstrong_equality::unequalweak_equality::nonequivalent,视情况而定。

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


另一种选择是:我们确实关心,并希望退回到综合排序。也就是说,如果我们需要比较TU 而它们没有提供&lt;=&gt;,我们可以使用标准库中的新定制点对象之一。还是哪一个?最保守的选择是将partial_orderingcompare_partial_order_fallback 合成。这保证了我们总能得到正确的答案。

对于optional&lt;T&gt;optional&lt;U&gt; 的比较,如下所示:

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&lt;int&gt;s 之间。因此,更好的选择可能是使用 &lt;=&gt; 返回的任何内容,否则使用保守的后备。我还不知道如何命名这个概念,所以我就直接说吧:

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();
        }
    }

    // ...
};

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

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 比较实现策略。如:“为什么不两者兼而有之?”战略。在operator&lt;=&gt; 上使用concept 可以确保a &lt; b 在可能的情况下调用&lt;=&gt;,而不是&lt;

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

但新代码没有。

【讨论】:

  • 当您知道其中一个是空的时,您正在调用*lhs*rhs。另外,当operator&lt;=&gt; 打算作为成员实现时,你为什么要作为非成员来实现它?
  • @DanielH 我在第二次比较中修正了他的错字
  • @DanielH 为什么你认为它是“打算作为成员实施的”?我的抱怨宁愿是“为什么你是两者的模板,而不是 koenig 运算符”。
  • @Yakk 因为提案第 3 页的注释说“通常,operator&lt;=&gt; 应该只是一个成员函数;由于第 2.3 节中的对称生成规则,您仍将获得每个参数的转换。”,就在第 1.4.1 节开头的上方。
  • @DanielH 您可以将第二个模板限制为无法转换为optional&lt;T&gt; 的类型,并保持第一个模板不变。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-12
  • 2013-03-17
  • 1970-01-01
  • 2017-10-23
  • 2021-11-02
  • 2021-11-03
相关资源
最近更新 更多