运算符 == 和运算符 != 的存在打破了一些概念

Fed*_*dor 35 c++ comparison-operators language-lawyer c++-concepts c++20

升级到最新的 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> );
Run Code Online (Sandbox Code Playgroud)

该示例被 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)'
Run Code Online (Sandbox Code Playgroud)

在线演示: https: //gcc.godbolt.org/z/evTfofq3d

这是新的 Visual Studio 编译器中的错误,还是相反,其他编译器是错误的?

Bar*_*rry 31

这是您正在寻找的等式运算符\n( P2468 )的结果。

\n

C++20 中的比较更改(允许a == ba != b找到重写和综合候选者)可能会破坏许多 C++17 代码。你可以在论文中看到一些例子。

\n

为了减轻这些中断(不是全部,但至少是大多数),MSVC 编译器开发人员 Cameron DaCamara \xe2\x80\xa0提出了一个狭窄的规则,尝试对 C 使用 C++17 比较规则++17 左右的代码。基本上,在 C++20 中,不需要编写,operator!=因为现有的operator==就足够了(a != b可以使用重写的表达式!(a == b),这几乎总是您想要的,所以为什么要费心编写它呢?)。但在 C++17 中,我们还没有这条规则,所以你必须同时编写这两个规则。

\n

因此,建议的规则如下:如果我们找到 anoperator==和 anoperator!=在相同范围内且参数相同,则使用 C++17 规则。否则,请使用 C++20 规则。这是我希望没有人需要知道的 C++ 规则之一 - 您的 C++17 代码将继续正常工作,而您的新 C++20 代码将开始工作。

\n
\n

使用发布的简化示例:

\n
struct A { };\n\nstruct B {\n  friend bool operator==(const B &, const A &);\n  friend bool operator!=(const B &, const A &);\n};\n\nint main() {\n  return A{} == B{};\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在 C++17 中,这无法编译,因为没有可行的运算符。

\n

在 C++20 中,在我正在讨论的论文之前,这将评估为B{} == A{}。但在 C++20 中,通过此 DR 的解决方案,因为您提供了operator==和 ,operator!=并且它们是相同的,我们假设您想要 C++17 规则,所以我们回到这个不编译,因为我们不考虑重写的候选者。

\n

修复方法很简单,就是不提供该运算符:

\n
struct A { };\n\nstruct B {\n  friend bool operator==(const B &, const A &);\n};\n\nint main() {\n  return A{} == B{};\n}\n
Run Code Online (Sandbox Code Playgroud)\n

或者,如果您需要同时处理 C++17/C++20,那么这个还不够,因为您需要提供其他三个 - 都是有条件的:

\n
struct A { };\n\nstruct B {\n  friend bool operator==(const B&, const A&);\n#if !(defined(__cpp_impl_three_way_comparison) and __cpp_impl_three_way_comparison >= 201907)\n  friend bool operator!=(const B& b, const A& a) { return !(b == a); }\n  friend bool operator==(const A& a, const B& b) { return b == a; }\n  friend bool operator!=(const A& a, const B& b) { return !(b == a); }\n#endif\n};\n\nint main() {\n  return A{} == B{};\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n

作为更新,这是CWG 2804,它指出 P2468 中的具体措辞没有完全正确地涵盖意图,并且没有按照friend您可能期望的方式处理运算符。从问题来看:

\n
struct X {\n  operator int();\n  friend bool operator==(X, int);\n  friend bool operator!=(X, int);  // #1\n} x;\n\nbool bx = x == x;    // error: lookup for rewrite target determination does not find hidden friend #1\n\nstruct Y {\n  operator int();\n  friend bool operator==(Y, int);   // #2\n} y;\n\nbool operator!=(Y, int);            // #3\n\nbool by = y == y;                   // OK, #2 is not a rewrite target because lookup finds #3\n
Run Code Online (Sandbox Code Playgroud)\n

可以说,其意图是bx应该没问题(在这种情况下我们不应该考虑重写,因为operator==operator!=是一起声明的)并且by应该是一个错误(因为在这种情况下我们应该考虑重写,并考虑重写会导致歧义)。

\n
\n

\xe2\x80\xa0从技术上讲,除了 Cameron 之外,本文还列出了其他 9 位作者,包括我,但 Cameron 完成了这里的大部分工作,他和 Richard Smith 提出了最终的规则集。就我对这篇论文的有意义参与而言,我是那个破坏了需要修复的代码的人。因此,考虑到 Cameron 提出了这个设计,并针对大量现有代码对其进行了测试,MSVC 成为第一个实现新规则的编译器也就不足为奇了。

\n


duc*_*uck 9

在P2468之后,如果可以通过在相同范围内的搜索找到operator==匹配,则不会使用函数进行重写比较。operator!=

MSVC 对该功能的标准措辞的解释似乎与 GCC/Clang 不同。

[over.match.oper]/3.4.4

  • y == x对于相等运算符,重写候选者还包括一个合成候选者,其中两个参数的顺序相反,对于作为具有第一个操作数 的重写目标的表达式的每个非重写候选者y

[over.match.oper]/4 :

非模板函数或F名为的函数模板operator==是具有第一个操作数的重写目标o,除非从运算符表达式的实例化上下文中在范围S中搜索名称找到对应的函数或函数模板(如果其名称为),其中如果是类成员, S是类类型的范围,否则是成员的命名空间范围。operator!=Foperator==oFF

在范围(如[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
Run Code Online (Sandbox Code Playgroud)