升级到最新的Visual Studio 2022版本17.6后,我们的一个自定义视图停止被识别为std::范围::范围
。事实证明,问题在于视图的迭代器中同时存在运算符==
和运算符!=
。
请在下面找到最小的简化示例(已经没有视图和迭代器):
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编译器中的bug,还是相反,其他编译器是错误的?
这是您正在寻找的相等运算符(P2468)的结果。
C 20中的比较更改-允许a==b
和a!=b
找到重写和合成的候选者-可能会破坏很多C 17代码。您可以在论文中看到一些示例。
为了减轻这些中断(不是全部,但至少是大多数),MSVC编译器开发人员Cameron DaCamara提出了一个狭窄的规则,试图将C 17比较规则用于C 17左右的代码。基本上,在C 20中,不需要编写运算符!=
,因为现有的运算符==
已经足够好了(a!=b
可以使用重写的表达式!(a==b)
,这几乎总是你想要的,那么为什么还要写它呢?)。但是在C 17中,我们还没有这个规则,所以你必须同时编写两个。
因此,建议的规则如下:如果我们在相同的范围内找到具有相同参数的运算符==
和运算符!=
,请使用C 17规则。否则,使用C 20规则。这是我希望没有人需要知道的C规则之一——你的C 17代码继续正常工作,你的新C 20代码一开始就可以工作。
使用发布的简化示例:
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中,在我谈论的论文之前,这将评估为B{}==A{}
。但是在C 20中,随着DR的分辨率,因为您提供了运算符==
和运算符!=
并且它们是相同的,我们假设您想要C 17规则,所以我们回到了不编译的地方,因为我们不考虑重写的候选者。
修复方法只是不提供该运算符:
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{};
}
从技术上讲,除了Cameron之外,这篇论文还列出了其他9位作者,包括我,但Cameron在这里完成了大部分工作,他和Richard Smith提出了最终的规则集。就我对这篇论文有意义的参与而言,是我破坏了需要修复的代码。因此,鉴于Cameron提出了这个设计,并针对许多现有代码进行了测试,MSVC是第一个实现新规则的编译器也就不足为奇了。
在P2468之后,如果可以在同一范围内搜索找到匹配的运算符!=
,则不使用运算符==
函数进行重写比较。
MSVC似乎对此功能的标准措辞的解释与GCC/Clang不同。
[over. match.oper]/3.4.4:
y==x
的每个未重写候选,该表达式是具有第一个操作数y
的重写目标。[over. match.oper]/4:
非模板函数或函数模板F
命名为运算符==
是具有第一个操作数o
的重写目标,除非从运算符表达式的实例化上下文中搜索范围S中的名称运算符!=
找到一个函数或函数模板,如果它的名称是运算符==
,则对应于F
,其中S是o
的类类型的范围,如果F
是类成员,否则F
是成员的命名空间范围。
在范围内搜索名称(如[class.成员. lookup]/1中定义)仅查找绑定到该范围内该名称的声明([base.lookup.General]/3)。好友声明不绑定名称([dcl.value.General]/2.1),因此好友运算符!=(const 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