我在调试这个问题时遇到了这个问题。
我把它缩减到只使用boost操作符:
编译器资源管理器c17c20
# 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中都可以用c17(启用ubsan/asan)编译并运行良好。
当您将隐式构造函数更改为 explicit
,问题行显然不再在c17上编译
令人惊讶的是,这两个版本都在c20(v1和v2)上编译,但它们会导致在c17上无法编译的两行上出现无限递归(崩溃或紧循环,具体取决于优化级别)。
显然,这种通过升级到c20而悄悄出现的无声bug令人担忧。
问题:
这是否符合c20的行为(我希望如此)
到底什么是干扰?我怀疑这可能是由于c++20新的“spaceship operator”支持,但不明白它是如何改变这段代码的行为的。
1条答案
按热度按时间ktca8awb1#
事实上,不幸的是,c++20使这段代码无限递归。
下面是一个简化的示例:
让我们看看
42 == F{42}
.在c17中,我们只有一个候选者:非成员候选者(
#2
),所以我们选择它。它的身体,x == y
,本身只有一个候选:成员候选(#1
)其中包括隐式转换y
变成一个F
. 然后候选成员比较两个整数成员,这是完全正确的。在c20中,初始表达式
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; }
bool operator==(int i) const { return t == i; }
friend bool operator==(const int& y, const F& x) { return x == y; }
private:
int t;
};
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;
};