当与非布尔 return 值重载相等比较时,C++20 中的重大变化或 clang-trunk/gcc-trunk 中的回归?
Breaking change in C++20 or regression in clang-trunk/gcc-trunk when overloading equality comparison with non-Boolean return value?
以下代码在 c++17 模式下使用 clang-trunk 编译良好,但在 c++2a(即将推出的 c++20)模式下中断:
// Meta struct describing the result of a comparison
struct Meta {};
struct Foo {
Meta operator==(const Foo&) {return Meta{};}
Meta operator!=(const Foo&) {return Meta{};}
};
int main()
{
Meta res = (Foo{} != Foo{});
}
它也可以用 gcc-trunk 或 clang-9.0.0 正常编译:https://godbolt.org/z/8GGT78
clang-trunk 和 -std=c++2a
的错误:
<source>:12:19: error: use of overloaded operator '!=' is ambiguous (with operand types 'Foo' and 'Foo')
Meta res = (f != g);
~ ^ ~
<source>:6:10: note: candidate function
Meta operator!=(const Foo&) {return Meta{};}
^
<source>:5:10: note: candidate function
Meta operator==(const Foo&) {return Meta{};}
^
<source>:5:10: note: candidate function (with reversed parameter order)
我知道 C++20 将允许仅重载 operator==
并且编译器将通过取反 operator==
的结果自动生成 operator!=
。据我了解,这仅在 return 类型为 bool
.
时有效
问题的根源在于,在 Eigen 中我们声明了一组运算符 ==
、!=
、<
、... Array
对象或 Array
和标量,其中 return(表达式)bool
的数组(然后可以按元素访问,或以其他方式使用)。例如,
#include <Eigen/Core>
int main()
{
Eigen::ArrayXd a(10);
a.setRandom();
return (a != 0.0).any();
}
与我上面的示例相反,这甚至会因 gcc-trunk 而失败:https://godbolt.org/z/RWktKs。
我还没有设法将其简化为非 Eigen 示例,它在 clang-trunk 和 gcc-trunk 中都失败了(顶部的示例非常简单)。
相关问题报告:https://gitlab.com/libeigen/eigen/issues/1833
我的实际问题:这实际上是 C++20 中的一个重大变化(并且是否有可能将比较运算符重载到 return 元对象),或者它更可能是回归clang/gcc?
[over.match.best]/2 列出了集合中有效重载的优先级。 2.8 部分告诉我们 F1
优于 F2
如果(在 许多 其他事物中):
F2
is a rewritten candidate ([over.match.oper]) and F1
is not
那里的例子显示了一个明确的 operator<
被调用,即使 operator<=>
在那里。
而[over.match.oper]/3.4.3告诉我们,在这种情况下operator==
的候选人是改写的候选人。
但是,您的操作员忘记了一件重要的事情:它们应该是const
函数。并使它们不 const
导致重载决议的早期方面发挥作用。这两个函数都不是完全匹配的,因为不同的参数需要进行非 const
到 const
的转换。这导致了所讨论的歧义。
制作完成后 const
、Clang trunk compiles.
我无法与 Eigen 的其余部分交流,因为我不知道代码,它非常大,因此无法放入 MCVE。
是的,代码实际上在 C++20 中中断。
表达式 Foo{} != Foo{}
在 C++20 中有三个候选者(而在 C++17 中只有一个):
Meta operator!=(Foo& /*this*/, const Foo&); // #1
Meta operator==(Foo& /*this*/, const Foo&); // #2
Meta operator==(const Foo&, Foo& /*this*/); // #3 - which is #2 reversed
这来自 [over.match.oper]/3.4 中新的 重写的候选人 规则。所有这些候选人都是可行的,因为我们的 Foo
论点不是 const
。为了找到最可行的候选人,我们必须通过决胜局。
最佳可行函数的相关规则来自[over.match.best]/2:
Given these definitions, a viable function F1
is defined to be a better function than another viable function F2
if for all arguments i
, ICS<sub>i</sub>(F1)
is not a worse conversion sequence than ICS<sub>i</sub>(F2)
, and then
- [... lots of irrelevant cases for this example ...] or, if not that, then
- F2 is a rewritten candidate ([over.match.oper]) and F1 is not
- F1 and F2 are rewritten candidates, and F2 is a synthesized candidate with reversed order of parameters and F1 is not
#2
和#3
为改写候选,#3
参数顺序颠倒,#1
未改写。但是为了达到那个决胜局,我们需要首先通过这个初始条件:对于所有参数转换序列并不差。
#1
比 #2
好,因为所有的转换序列都相同(平凡,因为函数参数相同)并且 #2
是重写的候选者,而 #1
不是。
但是... #1
/#3
和 #2
/#3
都卡在第一个条件上。在这两种情况下,第一个参数对于 #1
/#2
具有更好的转换顺序,而第二个参数对于 #3
具有更好的转换顺序(const
的参数具有接受额外的 const
资格,因此它的转换顺序更差)。这个 const
触发器导致我们无法偏爱任何一个。
因此,整个重载决议是不明确的。
As far as I understand, this only works as long as the return type is bool
.
这是不正确的。我们无条件地考虑重写和反转的候选人。我们的规则是,从 [over.match.oper]/9:
If a rewritten operator==
candidate is selected by overload resolution for an operator @
, its return type shall be cv bool
也就是我们还是考虑这些人选。但是,如果最可行的候选者是 return 的 operator==
,比如说 Meta
- 结果基本上与删除该候选者相同。
我们不想处于重载解析必须考虑return类型的状态。无论如何,这里的代码 returns Meta
是无关紧要的 - 如果它 returned bool
.
也会存在问题
谢天谢地,这里的修复很简单:
struct Foo {
Meta operator==(const Foo&) const;
Meta operator!=(const Foo&) const;
// ^^^^^^
};
一旦你使两个比较运算符成为const
,就没有更多的歧义了。所有参数都相同,因此所有转换序列基本相同。 #1
现在可以通过不重写打败 #3
并且 #2
现在可以通过不被逆转打败 #3
- 这使得 #1
成为最可行的候选者。与我们在 C++17 中得到的结果相同,只是多了几个步骤。
Eigen 问题似乎减少为以下内容:
using Scalar = double;
template<class Derived>
struct Base {
friend inline int operator==(const Scalar&, const Derived&) { return 1; }
int operator!=(const Scalar&) const;
};
struct X : Base<X> {};
int main() {
X{} != 0.0;
}
表达式的两个候选是
- 来自
operator==(const Scalar&, const Derived&)
的重写候选人
Base<X>::operator!=(const Scalar&) const
Per [over.match.funcs]/4,因为 operator!=
没有通过 using-declaration 导入到 X
的范围内, #2 的隐式对象参数是 const Base<X>&
。因此,#1 对该参数有更好的隐式转换序列(精确匹配,而不是派生到基础的转换)。选择 #1 会导致程序格式错误。
可能的修复:
- 将
using Base::operator!=;
添加到Derived
,或
- 将
operator==
改为 const Base&
而不是 const Derived&
。
我们的 Goopax 头文件也有类似的问题。使用 clang-10 和 -std=c++2a 编译以下内容会产生编译器错误。
template<typename T> class gpu_type;
using gpu_bool = gpu_type<bool>;
using gpu_int = gpu_type<int>;
template<typename T>
class gpu_type
{
friend inline gpu_bool operator==(T a, const gpu_type& b);
friend inline gpu_bool operator!=(T a, const gpu_type& b);
};
int main()
{
gpu_int a;
gpu_bool b = (a == 0);
}
提供这些额外的运算符似乎可以解决问题:
template<typename T>
class gpu_type
{
...
friend inline gpu_bool operator==(const gpu_type& b, T a);
friend inline gpu_bool operator!=(const gpu_type& b, T a);
};
以下代码在 c++17 模式下使用 clang-trunk 编译良好,但在 c++2a(即将推出的 c++20)模式下中断:
// Meta struct describing the result of a comparison
struct Meta {};
struct Foo {
Meta operator==(const Foo&) {return Meta{};}
Meta operator!=(const Foo&) {return Meta{};}
};
int main()
{
Meta res = (Foo{} != Foo{});
}
它也可以用 gcc-trunk 或 clang-9.0.0 正常编译:https://godbolt.org/z/8GGT78
clang-trunk 和 -std=c++2a
的错误:
<source>:12:19: error: use of overloaded operator '!=' is ambiguous (with operand types 'Foo' and 'Foo')
Meta res = (f != g);
~ ^ ~
<source>:6:10: note: candidate function
Meta operator!=(const Foo&) {return Meta{};}
^
<source>:5:10: note: candidate function
Meta operator==(const Foo&) {return Meta{};}
^
<source>:5:10: note: candidate function (with reversed parameter order)
我知道 C++20 将允许仅重载 operator==
并且编译器将通过取反 operator==
的结果自动生成 operator!=
。据我了解,这仅在 return 类型为 bool
.
问题的根源在于,在 Eigen 中我们声明了一组运算符 ==
、!=
、<
、... Array
对象或 Array
和标量,其中 return(表达式)bool
的数组(然后可以按元素访问,或以其他方式使用)。例如,
#include <Eigen/Core>
int main()
{
Eigen::ArrayXd a(10);
a.setRandom();
return (a != 0.0).any();
}
与我上面的示例相反,这甚至会因 gcc-trunk 而失败:https://godbolt.org/z/RWktKs。 我还没有设法将其简化为非 Eigen 示例,它在 clang-trunk 和 gcc-trunk 中都失败了(顶部的示例非常简单)。
相关问题报告:https://gitlab.com/libeigen/eigen/issues/1833
我的实际问题:这实际上是 C++20 中的一个重大变化(并且是否有可能将比较运算符重载到 return 元对象),或者它更可能是回归clang/gcc?
[over.match.best]/2 列出了集合中有效重载的优先级。 2.8 部分告诉我们 F1
优于 F2
如果(在 许多 其他事物中):
F2
is a rewritten candidate ([over.match.oper]) andF1
is not
那里的例子显示了一个明确的 operator<
被调用,即使 operator<=>
在那里。
而[over.match.oper]/3.4.3告诉我们,在这种情况下operator==
的候选人是改写的候选人。
但是,您的操作员忘记了一件重要的事情:它们应该是const
函数。并使它们不 const
导致重载决议的早期方面发挥作用。这两个函数都不是完全匹配的,因为不同的参数需要进行非 const
到 const
的转换。这导致了所讨论的歧义。
制作完成后 const
、Clang trunk compiles.
我无法与 Eigen 的其余部分交流,因为我不知道代码,它非常大,因此无法放入 MCVE。
是的,代码实际上在 C++20 中中断。
表达式 Foo{} != Foo{}
在 C++20 中有三个候选者(而在 C++17 中只有一个):
Meta operator!=(Foo& /*this*/, const Foo&); // #1
Meta operator==(Foo& /*this*/, const Foo&); // #2
Meta operator==(const Foo&, Foo& /*this*/); // #3 - which is #2 reversed
这来自 [over.match.oper]/3.4 中新的 重写的候选人 规则。所有这些候选人都是可行的,因为我们的 Foo
论点不是 const
。为了找到最可行的候选人,我们必须通过决胜局。
最佳可行函数的相关规则来自[over.match.best]/2:
Given these definitions, a viable function
F1
is defined to be a better function than another viable functionF2
if for all argumentsi
,ICS<sub>i</sub>(F1)
is not a worse conversion sequence thanICS<sub>i</sub>(F2)
, and then
- [... lots of irrelevant cases for this example ...] or, if not that, then
- F2 is a rewritten candidate ([over.match.oper]) and F1 is not
- F1 and F2 are rewritten candidates, and F2 is a synthesized candidate with reversed order of parameters and F1 is not
#2
和#3
为改写候选,#3
参数顺序颠倒,#1
未改写。但是为了达到那个决胜局,我们需要首先通过这个初始条件:对于所有参数转换序列并不差。
#1
比 #2
好,因为所有的转换序列都相同(平凡,因为函数参数相同)并且 #2
是重写的候选者,而 #1
不是。
但是... #1
/#3
和 #2
/#3
都卡在第一个条件上。在这两种情况下,第一个参数对于 #1
/#2
具有更好的转换顺序,而第二个参数对于 #3
具有更好的转换顺序(const
的参数具有接受额外的 const
资格,因此它的转换顺序更差)。这个 const
触发器导致我们无法偏爱任何一个。
因此,整个重载决议是不明确的。
As far as I understand, this only works as long as the return type is
bool
.
这是不正确的。我们无条件地考虑重写和反转的候选人。我们的规则是,从 [over.match.oper]/9:
If a rewritten
operator==
candidate is selected by overload resolution for an operator@
, its return type shall be cvbool
也就是我们还是考虑这些人选。但是,如果最可行的候选者是 return 的 operator==
,比如说 Meta
- 结果基本上与删除该候选者相同。
我们不想处于重载解析必须考虑return类型的状态。无论如何,这里的代码 returns Meta
是无关紧要的 - 如果它 returned bool
.
谢天谢地,这里的修复很简单:
struct Foo {
Meta operator==(const Foo&) const;
Meta operator!=(const Foo&) const;
// ^^^^^^
};
一旦你使两个比较运算符成为const
,就没有更多的歧义了。所有参数都相同,因此所有转换序列基本相同。 #1
现在可以通过不重写打败 #3
并且 #2
现在可以通过不被逆转打败 #3
- 这使得 #1
成为最可行的候选者。与我们在 C++17 中得到的结果相同,只是多了几个步骤。
Eigen 问题似乎减少为以下内容:
using Scalar = double;
template<class Derived>
struct Base {
friend inline int operator==(const Scalar&, const Derived&) { return 1; }
int operator!=(const Scalar&) const;
};
struct X : Base<X> {};
int main() {
X{} != 0.0;
}
表达式的两个候选是
- 来自
operator==(const Scalar&, const Derived&)
的重写候选人
Base<X>::operator!=(const Scalar&) const
Per [over.match.funcs]/4,因为 operator!=
没有通过 using-declaration 导入到 X
的范围内, #2 的隐式对象参数是 const Base<X>&
。因此,#1 对该参数有更好的隐式转换序列(精确匹配,而不是派生到基础的转换)。选择 #1 会导致程序格式错误。
可能的修复:
- 将
using Base::operator!=;
添加到Derived
,或 - 将
operator==
改为const Base&
而不是const Derived&
。
我们的 Goopax 头文件也有类似的问题。使用 clang-10 和 -std=c++2a 编译以下内容会产生编译器错误。
template<typename T> class gpu_type;
using gpu_bool = gpu_type<bool>;
using gpu_int = gpu_type<int>;
template<typename T>
class gpu_type
{
friend inline gpu_bool operator==(T a, const gpu_type& b);
friend inline gpu_bool operator!=(T a, const gpu_type& b);
};
int main()
{
gpu_int a;
gpu_bool b = (a == 0);
}
提供这些额外的运算符似乎可以解决问题:
template<typename T>
class gpu_type
{
...
friend inline gpu_bool operator==(const gpu_type& b, T a);
friend inline gpu_bool operator!=(const gpu_type& b, T a);
};