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

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

我一直将其精简为仅使用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. 当您将隐式构造函数更改为 时explicit,有问题的行显然不再在 C++17 上编译

令人惊讶的是,这两个版本都在 C++20(v1和v2)上编译,但它们会导致无法在 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) ,它涉及隐式转换yF。然后该成员候选人比较两个整数成员,这完全没问题。

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

然而,x == y现在现在有两个候选人:再次是成员候选人 ( #1),还有反向的非成员候选人 ( #2reversed )。#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;
};

  • Excellent walk through. I'm a bit torn on how to judge the situation. As a professional, I'm really unhappy to see C++ introducing compatibility death traps. I'm happy to see [this sentiment](https://github.com/boostorg/utility/issues/65#issuecomment-636073901) but I fear it looks like nothing much is going to come of it. So libraries just silently break - **at runtime** - which is not a good storyline. I know I've written code that will suffer these issues, and I shudder to think what happens if the current devs upgrade. They'll probably be cursing my code in righteous indignation ¯_(ツ)_/¯
  • @Barry I feel like this shouldn't be hard for compilers to statically diagnose (after all, it all happens at compile time). I.e. something like "c++20 compat warning: different overload will be selected depending on the std mode, proposed fix: ...".
  • @sehe I'm really unhappy about it too, and it's my fault even. And this specific case is the worst case - the only language change that preserves the C++17 behavior is not having any part of the feature (or having the `==` functionality opt-in, which nobody has come up with a passable way of doing so). Not only is it the only one we can't really fix, but all the other breakages are code that ceases to compile while this one insidiously continues to compile. Just all sorts of bad.
  • @sehe And this case even, you could just rewrite the body of `F` to have `auto operator<=>(F const&) const = default;` and that one line gives you all the comparisons. Which is good for C++20 going forward, but not so good for the transitional step obviously.
  • @Dan I'm not a compiler guy, but if you want to take a crack at adding such a warning to clang, I'm sure many people would be very appreciative.

以上是C++20行为用相等运算符破坏现有代码?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>