C ++ 20用相等运算符破坏现有代码的行为? [英] C++20 behaviour breaking existing code with equality operator?

查看:42
本文介绍了C ++ 20用相等运算符破坏现有代码的行为?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在调试我一直将其缩减为仅使用 Boost运算符:

I trimmed it down all the way to just using Boost Operators:

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

  1. Compiler Explorer 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)编译并正常运行.

This program compiles and runs fine with C++17 (ubsan/asan enabled) in both GCC and Clang.

当将隐式构造函数更改为 explicit 时,出现问题的行显然是

When you change the implicit constructor to explicit, the problematic lines obviously no longer compile on C++17

令人惊讶的是,两个版本均可在C ++ 20上编译( v1

Surprisingly both versions compile on C++20 (v1 and v2), but they lead to infinite recursion (crash or tight loop, depending on optimization level) on the two lines that wouldn't compile on C++17.

显然,通过升级到C ++ 20来爬入这种无声的bug令人担忧.

Obviously this kind of silent bug creeping in by upgrading to C++20 is worrisome.

问题:

  • 这是否符合c ++ 20行为(我希望如此)
  • 到底是什么干扰?我怀疑这可能是由于c ++ 20的新太空飞船运营商"支持,但不了解如何如何更改此代码的行为.
  • Is this c++20 behaviour conformant (I expect so)
  • What exactly is interfering? I suspect it might be due to c++20's new "spaceship operator" support, but don't understand how it changes the behaviour of this code.

推荐答案

实际上,不幸的是,C ++ 20使此代码无限递归.

Indeed, C++20 unfortunately makes this code infinitely recursive.

这是一个简化的示例:

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 .然后那个候选成员比较两个整数成员,这完全没问题.

In C++17, we only had one candidate: the non-member candidate (#2), so we select that. Its body, x == y, itself only has one candidate: the member candidate (#1) which involves implicitly converting y into an F. And then that member candidate compares the two integer members and this is totally fine.

在C ++ 20中,初始表达式 42 == F {42} 现在具有两个候选对象:都是非成员候选对象(#2),现在也是反向成员候选(#1 反向).#2 是更好的匹配-我们完全匹配两个参数,而不是调用转换,因此已被选择.

In C++20, the initial expression 42 == F{42} now has two candidates: both the non-member candidate (#2) as before and now also the reversed member candidate (#1 reversed). #2 is the better match - we exactly match both arguments instead of invoking a conversion, so it's selected.

但是,现在 x == y 现在有两个候选对象:再次成为成员候选对象(#1 ),但也有相反的候选对象非成员候选人(#2 反向).#2 再次是更好的匹配,其原因与之前更好的匹配相同:无需进行任何转换.因此,我们改为评估 y == x .无限递归.

Now, however, x == y now has two candidates: the member candidate again (#1), but also the reversed non-member candidate (#2 reversed). #2 is the better match again for the same reason that it was a better match before: no conversions necessary. So we evaluate y == x instead. Infinite recursion.

非逆向候选人比逆向候选人更可取,但只能作为决胜局.更好的转换顺序始终是第一位.

Non-reversed candidates are preferred to reversed candidates, but only as a tiebreaker. Better conversion sequence is always first.

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

Okay great, how can we fix it? The simplest option is removing the non-member candidate entirely:

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),效果很好.

42 == F{42} here evaluates as F{42}.operator==(42), which works fine.

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

If we want to keep the non-member candidate, we can add its reversed candidate explicitly:

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 会更喜欢成员候选者,然后执行正常的平等.

This makes 42 == F{42} still choose the non-member candidate, but now x == y in the body there will prefer the member candidate, which then does the normal equality.

最后一个版本也可以删除非成员候选人.下面的代码也适用于所有测试用例,而无需递归(这就是我以后如何在C ++ 20中编写比较的方式):

This last version can also remove the non-member candidate. The following also works without recursion for all test cases (and is how I would write comparisons in C++20 going forward):

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;
};

这篇关于C ++ 20用相等运算符破坏现有代码的行为?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆