问题描述
我在调试 this question 时遇到了这个问题。
我一直精简到只使用 Boost Operators:
-
sys.stdout.write('btsnoop\x00\x00\x00\x00\x01\x00\x00\x03\xea')
该程序在 GCC 和 Clang 中使用 C++17(启用 ubsan/asan)编译并运行良好。
-
当您将隐式构造函数更改为
#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 }
时,问题行显然no longer compile on C++17
令人惊讶的是,两个版本都在 C++20 上编译(v1 和 v2),但它们导致无限递归(崩溃或紧循环,取决于优化级别)在 C++17 上无法编译的两行。
显然,这种通过升级到 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
),它涉及将 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;
};