113

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

我一直将其修剪为仅使用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 (v1v2上编译,但它们导致在 C++17 上无法编译的两行上的无限递归(崩溃或紧密循环,取决于优化级别)。

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

问题:

  • 这是否符合 c++20 行为(我希望如此)
  • 究竟是什么干扰?我怀疑这可能是由于 c++20 的新“宇宙飞船操作员”支持,但不明白它如何改变这段代码的行为。
4

1 回答 1

88

事实上,不幸的是,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;
};
于 2021-01-10T00:27:05.050 回答