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

问题描述

我在调试 this question 时遇到了这个问题。

我一直精简到只使用 Boost Operators

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

    sys.stdout.write('btsnoop\x00\x00\x00\x00\x01\x00\x00\x03\xea')

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

  2. 当您将隐式构造函数更改为#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 } 时,问题行显然no longer compile on C++17

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

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

问题:

  • 这是否符合 C++20 行为(我希望如此)
  • 究竟是什么干扰?我怀疑这可能是由于 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),因此我们选择了它。它的主体 x == y 本身只有一个候选项:成员候选项 (#1),它涉及将 y 隐式转换为 F。然后那个候选成员比较两个整数成员,这完全没问题。

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

然而,现在 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;
};