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

hpxqektj  于 2021-07-06  发布在  Java
关注(0)|答案(1)|浏览(332)

我在调试这个问题时遇到了这个问题。
我把它缩减到只使用boost操作符:
编译器资源管理器c17c20


# 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中都可以用c17(启用ubsan/asan)编译并运行良好。
当您将隐式构造函数更改为 explicit ,问题行显然不再在c
17上编译
令人惊讶的是,这两个版本都在c20(v1和v2)上编译,但它们会导致在c17上无法编译的两行上出现无限递归(崩溃或紧循环,具体取决于优化级别)。
显然,这种通过升级到c20而悄悄出现的无声bug令人担忧。
问题:
这是否符合c
20的行为(我希望如此)
到底什么是干扰?我怀疑这可能是由于c++20新的“spaceship operator”支持,但不明白它是如何改变这段代码的行为的。

ktca8awb

ktca8awb1#

事实上,不幸的是,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} .
在c17中,我们只有一个候选者:非成员候选者( #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;
};

相关问题