C++20 行为使用相等运算符破坏现有代码?

C++20 behaviour breaking existing code with equality operator?

我在调试时 运行 进入这个

我一直将其削减到只使用 Boost Operators:

  1. 编译器资源管理器C++17 C++20

    #include <boost/operators.hpp>
    
    struct F : boost::totally_ordered1<F, boost::totally_ordered2<F, int>> {
        /*implicit*/ F(int t_) : t(t_) {}
        bool operator==(F const& o) const { return t == o.t; }
        bool operator< (F const& o) const { return t <  o.t; }
      private: int t;
    };
    
    int main() {
        #pragma GCC diagnostic ignored "-Wunused"
        F { 42 } == F{ 42 }; // OKAY
        42 == F{42};         // C++17 OK, C++20 infinite recursion
        F { 42 } == 42;      // C++17 OK, C++20 infinite recursion
    }
    

    此程序在 GCC 和 Clang 中使用 C++17(ubsan/asan 启用)编译和运行良好。

  2. 当您将 implicit 构造函数更改为 explicit 时,问题行显然 no longer compile on C++17

令人惊讶的是,这两个版本 都可以在 C++20 (v1 and v2) 上编译,但它们会导致 无限递归 (崩溃或紧密循环,取决于优化级别)在无法在 C++17 上编译的两行上。

显然,这种通过升级到 C++20 而悄悄潜入的错误令人担忧。

问题:

事实上,不幸的是,C++20 使这段代码无限递归。

这是一个简化的例子:

struct F {
    /*implicit*/ F(int t_) : t(t_) {}

    // member: #1
    bool operator==(F const& o) const { return t == o.t; }

    // non-member: #2
    friend bool operator==(const int& y, const F& x) { return x == y; }

private:
    int t;
};

让我们看看42 == F{42}

在 C++17 中,我们只有一个候选者:非成员候选者 (#2),所以我们 select 那个。它的主体 x == y 本身只有一个候选者:成员候选者 (#1),它涉及将 y 隐式转换为 F。然后那个成员候选人比较两个整数成员,这完全没问题。

在 C++20 中,初始表达式 42 == F{42} 现在有 两个 候选:非成员候选 (#2) 和以前一样现在也是反转的成员候选人(#1 反转)。 #2 是更好的匹配 - 我们完全匹配两个参数而不是调用转换,所以它是 selected.

然而,现在x == y现在有两个个候选人:又是会员候选人(#1),还有反向的非会员候选人( #2 反转)。 #2 再次成为更好的匹配,原因与之前更好的匹配相同:无需转换。所以我们评估 y == x 代替。无限递归。

非逆转候选人优于逆转候选人,但仅作为决胜局。更好的转换顺序总是第一个。


很好,我们该如何解决?最简单的选择是完全删除非成员候选人:

struct F {
    /*implicit*/ F(int t_) : t(t_) {}

    bool operator==(F const& o) const { return t == o.t; }

private:
    int t;
};

42 == F{42} 这里的计算结果为 F{42}.operator==(42),效果很好。

如果我们想保留非成员候选人,我们可以显式添加其反向候选人:

struct F {
    /*implicit*/ F(int t_) : t(t_) {}
    bool operator==(F const& o) const { return t == o.t; }
    bool operator==(int i) const { return t == i; }
    friend bool operator==(const int& y, const F& x) { return x == y; }

private:
    int t;
};

这使得42 == F{42}仍然选择非成员候选人,但现在x == y在正文中会优先选择成员候选人,然后进行正常平等。

最后这个版本也可以去掉非会员候选人。以下内容也适用于所有测试用例而无需递归(这也是我在 C++20 中编写比较的方式):

struct F {
    /*implicit*/ F(int t_) : t(t_) {}
    bool operator==(F const& o) const { return t == o.t; }
    bool operator==(int i) const { return t == i; }

private:
    int t;
};