C++ 20 中的运算符 == 和 <=> 应该作为成员函数还是自由函数实现?

NoS*_*tAl 7 c++ spaceship-operator c++20

注意:我认为这在技术上与这个问题重复,但是:

  1. C++20 中的更改==相当激进,我不确定恢复 9 年的问题是否是正确的做法。
  2. 我具体询问了编译器正在重写的运算符,而不是例如运算==符。<=><

ps 我目前有自己的看法(基于 foonathan 的一些谈话),但这只是当前的偏好,我不希望以此来偏见潜在的答案。

Bar*_*rry 7

我认为在 C++20 中,比较应该是成员函数,除非你有一个强有力的理由。

让我首先从 C++17 演算开始:我们经常以非成员的身份编写比较。这样做的原因是,这是允许两侧比较的唯一方法。X如果我有一个想要与之比较的类型int,我就无法使用1 == X{}成员函数 - 它必须是一个自由函数:

struct X { };
bool operator==(X, int);
bool operator==(int lhs, X rhs) { return rhs == lhs; }
Run Code Online (Sandbox Code Playgroud)

在这件事上没有太多选择。现在,将它们编写为纯粹的自由函数是次优的,因为我们污染了命名空间并增加了查找中候选的数量 - 所以最好让它们成为隐藏的朋友

struct X {
    friend bool operator==(X, int);
    friend bool operator==(int lhs, X rhs) { return rhs == lhs; }
};
Run Code Online (Sandbox Code Playgroud)

在 C++20 中,我们没有这个问题,因为比较本身是对称的。你可以这样写:

struct X {
    bool operator==(int) const;
};
Run Code Online (Sandbox Code Playgroud)

仅该声明就已经允许X{} == 11 == X{},同时也已经不为名称查找提供额外的候选者(如果一侧或另一侧是 ,则它已经只是候选者X)。

此外,在 C++20 中,如果比较是在类的声明中声明的,则可以默认比较。这些可以是成员函数或隐藏的朋友,但不能是外部自由函数。


提供非会员比较的一个有趣的案例是我遇到的std::string。该类型的比较当前是非成员函数模板:

 template<class charT, class traits, class Allocator>
    constexpr bool
      operator==(const basic_string<charT, traits, Allocator>& lhs,
                 const basic_string<charT, traits, Allocator>& rhs) noexcept;
Run Code Online (Sandbox Code Playgroud)

这与使其成为成员(非模板)函数或隐藏友元(非模板)函数具有重要的不同语义,因为它不允许通过作为模板进行隐式转换。正如我所指出的,将此比较运算符转换为非模板会产生突然允许两侧隐式转换的效果,这可能会破坏以前没有意识到这种可能性的代码。

但无论如何,如果您有一个类模板并且希望避免比较时的转换,那么这可能是坚持为比较运算符使用非成员函数模板的一个很好的理由。但仅此而已。