提问人:Fedor 提问时间:5/19/2023 更新时间:5/20/2023 访问量:4012
运算符 == 和运算符 != 的存在打破了一些概念
The presence of both operator == and operator != breaks some concepts
问:
升级到最新的 Visual Studio 2022 版本 17.6 后,我们的一个自定义视图停止被识别为 .事实证明,问题出在两者的存在以及视图的迭代器中。std::ranges::range
operator ==
operator !=
请找到下面的最小简化示例(还没有视图和迭代器):
struct A {
friend bool operator ==( const A &, const A & ) = default;
};
struct B {
friend bool operator ==( const B &, const B & ) = default;
friend bool operator ==( const B &, const A & ) { return false; }
// Visual Studio 2022 version 17.6 does not like next line
friend bool operator !=( const B &, const A & ) { return true; }
};
template< class T, class U >
concept comparable =
requires(const std::remove_reference_t<T>& t,
const std::remove_reference_t<U>& u) {
{ t == u } -> std::same_as<bool>;
{ t != u } -> std::same_as<bool>;
{ u == t } -> std::same_as<bool>;
{ u != t } -> std::same_as<bool>;
};
// ok in GCC, Clang and Visual Studio before version 17.6
static_assert( comparable<A, B> );
GCC 和 Clang 接受该示例,但最新的 Visual Studio 不接受该示例,后者会打印错误:
<source>(25): error C2607: static assertion failed
<source>(25): note: the concept 'comparable<A,B>' evaluated to false
<source>(18): note: 'bool operator ==(const B &,const A &)': rewritten candidate function was excluded from overload resolution because a corresponding operator!= declared in the same scope
<source>(4): note: could be 'bool operator ==(const A &,const A &)' [found using argument-dependent lookup]
<source>(8): note: or 'bool operator ==(const B &,const B &)' [found using argument-dependent lookup]
<source>(9): note: or 'bool operator ==(const B &,const A &)' [found using argument-dependent lookup]
<source>(4): note: or 'bool operator ==(const A &,const A &)' [synthesized expression 'y == x']
<source>(8): note: or 'bool operator ==(const B &,const B &)' [synthesized expression 'y == x']
<source>(9): note: or 'bool operator ==(const B &,const A &)' [synthesized expression 'y == x']
<source>(18): note: 'bool operator ==(const B &,const A &)': rewritten candidate function was excluded from overload resolution because a corresponding operator!= declared in the same scope
<source>(18): note: while trying to match the argument list '(const A, const B)'
在线演示:https://gcc.godbolt.org/z/evTfofq3d
是新的Visual Studio编译器中的错误,还是相反,其他错误是错误的?
答:
这是您正在寻找的相等运算符的结果 (第 2468 页)。
C++20 中的比较更改 - 允许并找到重写和合成的候选者 - 可能会破坏许多 C++17 代码。您可以在论文中看到一些示例。a == b
a != b
为了缓解这些中断(不是全部,但至少是大多数),MSVC 编译器开发人员 Cameron DaCamara†提出了一个狭窄的规则,该规则尝试将 C++17 比较规则用于 C++17 代码。基本上,在 C++20 中,没有必要编写,因为现有的已经足够好了(可以使用重写的表达式,这几乎总是你想要的,那么为什么要写呢?但是在 C++17 中,我们还没有这个规则,所以你必须同时编写这两个规则。operator!=
operator==
a != b
!(a == b)
因此,建议的规则如下:如果我们在具有相同参数的同一范围内找到 an 和 an,请使用 C++17 规则。否则,请使用 C++20 规则。这是我希望没有人需要知道的 C++ 规则之一 - 您的 C++17 代码继续正常工作,您的新 C++20 代码可以正常工作。operator==
operator!=
使用已发布的简化示例:
struct A { };
struct B {
friend bool operator==(const B &, const A &);
friend bool operator!=(const B &, const A &);
};
int main() {
return A{} == B{};
}
在 C++17 中,这不会编译,因为没有可行的运算符。
在 C++20 中,在我谈论的论文之前,这将计算为 .但是在 C++20 中,对于此 DR 的解析,由于您同时提供了 和 并且它们是相同的,因此我们假设您想要 C++17 规则,因此我们回到不编译的问题,因为我们不考虑重写的候选规则。B{} == A{}
operator==
operator!=
解决方法很简单,就是不提供该运算符:
struct A { };
struct B {
friend bool operator==(const B &, const A &);
};
int main() {
return A{} == B{};
}
或者,如果你需要同时处理 C++17/C++20,那么无论如何,这一个是不够的,因为你需要提供另外三个 - 都是有条件的:
struct A { };
struct B {
friend bool operator==(const B&, const A&);
#if !(defined(__cpp_impl_three_way_comparison) and __cpp_impl_three_way_comparison >= 201907)
friend bool operator!=(const B& b, const A& a) { return !(b == a); }
friend bool operator==(const A& a, const B& b) { return b == a; }
friend bool operator!=(const A& a, const B& b) { return !(b == a); }
#endif
};
int main() {
return A{} == B{};
}
作为更新,这是CWG 2804,它指出P2468中的具体措辞没有完全正确地涵盖意图,并且没有以您可能期望的方式处理运算符。从问题:friend
struct X {
operator int();
friend bool operator==(X, int);
friend bool operator!=(X, int); // #1
} x;
bool bx = x == x; // error: lookup for rewrite target determination does not find hidden friend #1
struct Y {
operator int();
friend bool operator==(Y, int); // #2
} y;
bool operator!=(Y, int); // #3
bool by = y == y; // OK, #2 is not a rewrite target because lookup finds #3
可以说,其意图应该是可以的(在这种情况下我们不应该考虑重写,因为 和 是一起声明的),并且应该是一个错误(因为在这种情况下我们应该考虑重写,并考虑重写导致歧义)。bx
operator==
operator!=
by
†从技术上讲,除了卡梅隆之外,还有9位其他作者,包括我,但卡梅隆在这里做了大部分工作,他和理查德·史密斯提出了最终的规则集。在某种程度上,我有意义地参与了这篇论文,正是我破坏了需要修复的代码。因此,鉴于 Cameron 提出了这个设计,并针对许多现有代码对其进行了测试,MSVC 是第一个实现新规则的编译器也就不足为奇了。
评论
在 P2468 之后,如果可以通过同一范围内的搜索找到匹配项,则函数不用于重写比较。operator==
operator!=
MSVC 似乎对此功能的标准措辞的解释与 GCC/Clang 不同。
- 对于相等运算符,重写的候选项还包括一个合成的候选项,两个参数的顺序相反,对于表达式的每个未重写的候选项,该候选项是具有第一个操作数的重写目标。
y == x
y
命名的非模板函数或函数模板是具有第一个操作数的重写目标,除非从运算符表达式的实例化上下文中搜索作用域 S 中的名称,找到与其名称 are 相对应的函数或函数模板,其中 S 是 if 的类类型的范围 if 是类成员, 其命名空间范围是成员,否则。
F
operator==
o
operator!=
F
operator==
o
F
F
在作用域中搜索名称(如 [class.member.lookup]/1 中所定义)仅在该作用域 ([basic.lookup.general]/3) 中查找绑定到该名称的声明。友元声明不绑定名称 ([dcl.meaning.general]/2.1),因此不应阻止成为重写的候选者(至少在对这条规则的字面解释下,这似乎是 GCC 和 Clang 实现的):friend operator!=(const B&, const A&)
operator==(const B&, const A&)
A() == B()
struct A {};
struct B {
friend bool operator==(B, A);
friend bool operator!=(B, A);
};
bool x = A() == B(); // OK in GCC/Clang
bool operator!=(B, A);
bool y = A() == B(); // error in GCC/Clang
评论
For equality operator expressions x == y and x != y, a synthesized candidate with the order of the two parameters reversed is added for each member, non-member, and built-in operator==s found, unless there is a matching operator!=.
==(const B &,const A &)
!=( const B &, const A & )