对于<=>,我们必须做出决定:如果我们需要的底层比较还没有实现<=>,我们想做什么?
一个选项是:不在乎。只需使用<=>,如果相关类型不提供,我们就没有可比性。这使得实现非常简洁(请注意,您需要为== 做同样的事情,总共六个函数):
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::less、partial_ordering::less、strong_equality::unequal 或 weak_equality::nonequivalent,视情况而定。
以上是超级好,快乐的答案。并且希望随着时间的推移,人们会采用<=>,并且越来越多的代码将能够依赖于快乐的答案。
另一种选择是:我们确实关心,并希望退回到综合排序。也就是说,如果我们需要比较T 和U 而它们没有提供<=>,我们可以使用标准库中的新定制点对象之一。还是哪一个?最保守的选择是将partial_ordering 与compare_partial_order_fallback 合成。这保证了我们总能得到正确的答案。
对于optional<T> 与optional<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();
}
}
// ...
};
第三个选项,最保守的选项,是标准库将采用的。提供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<=> 上使用concept 可以确保a < b 在可能的情况下调用<=>,而不是<。
这是迄今为止最乏味和最冗长的方法,有很多样板,但它确保对于现有的单对象比较类型,现有比较继续工作并做同样的事情。标准库必须像这样保守。
但新代码没有。