C++20 行为使用相等运算符破坏现有代码?
C++20 behaviour breaking existing code with equality operator?
我在调试时 运行 进入这个 。
我一直将其削减到只使用 Boost Operators:
-
#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 启用)编译和运行良好。
当您将 implicit 构造函数更改为 explicit
时,问题行显然 no longer compile on C++17
令人惊讶的是,这两个版本 都可以在 C++20 (v1 and 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
),所以我们 select 那个。它的主体 x == y
本身只有一个候选者:成员候选者 (#1
),它涉及将 y
隐式转换为 F
。然后那个成员候选人比较两个整数成员,这完全没问题。
在 C++20 中,初始表达式 42 == F{42}
现在有 两个 候选:非成员候选 (#2
) 和以前一样现在也是反转的成员候选人(#1
反转)。 #2
是更好的匹配 - 我们完全匹配两个参数而不是调用转换,所以它是 selected.
然而,现在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;
};
我在调试时 运行 进入这个
我一直将其削减到只使用 Boost Operators:
-
#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 启用)编译和运行良好。
当您将 implicit 构造函数更改为
explicit
时,问题行显然 no longer compile on C++17
令人惊讶的是,这两个版本 都可以在 C++20 (v1 and 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
),所以我们 select 那个。它的主体 x == y
本身只有一个候选者:成员候选者 (#1
),它涉及将 y
隐式转换为 F
。然后那个成员候选人比较两个整数成员,这完全没问题。
在 C++20 中,初始表达式 42 == F{42}
现在有 两个 候选:非成员候选 (#2
) 和以前一样现在也是反转的成员候选人(#1
反转)。 #2
是更好的匹配 - 我们完全匹配两个参数而不是调用转换,所以它是 selected.
然而,现在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;
};