提问者:小点点

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


升级到最新的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,还是相反,其他编译器是错误的?


共2个答案

匿名用户

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

C 20中的比较更改-允许a==ba!=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