C++ 打破C+的变化+;20或重载与非布尔返回值的相等比较时clang trunk/gcc trunk中的回归?
下面的代码在c++17模式下使用铿锵trunk可以很好地编译,但在c++2a(即将推出的c++20)模式下会中断: 它还可以使用gcc主干或clang-9.0.0编译: 发出叮当声的主干和C++ 打破C+的变化+;20或重载与非布尔返回值的相等比较时clang trunk/gcc trunk中的回归?,c++,language-lawyer,eigen,eigen3,c++20,C++,Language Lawyer,Eigen,Eigen3,C++20,下面的代码在c++17模式下使用铿锵trunk可以很好地编译,但在c++2a(即将推出的c++20)模式下会中断: 它还可以使用gcc主干或clang-9.0.0编译: 发出叮当声的主干和-std=c++2a出现错误: <source>:12:19: error: use of overloaded operator '!=' is ambiguous (with operand types 'Foo' and 'Foo') Meta res = (f != g);
-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)
与我上面的示例相反,这甚至在gcc主干中失败:。
我还没有设法将其简化为一个非本征示例,它在clang trunk和gcc trunk中都失败(顶部的示例非常简化)
相关问题报告:
我的实际问题是:这实际上是C++20中的一个突破性更改(是否有可能重载比较运算符以返回元对象),还是更可能是clang/gcc中的回归?[over.match.best]/2列出了集合中有效重载的优先级。第节告诉我们,
F1
优于F2
if(除其他外):
F2
是重写的候选者([over.match.oper]),而F1
不是
那里的示例显示了一个显式的
操作符Yes,代码实际上在C++20中中断
表达式Foo{}=Foo{}
在C++20中有三个候选者(而在C++17中只有一个):
这来自于中新重写的候选规则。所有这些候选者都是可行的,因为我们的Foo
参数不是const
。为了找到最佳可行的候选人,我们必须通过决胜局
最佳可行功能的相关规则如下:
根据这些定义,一个可行函数F1
被定义为比另一个可行函数F2
更好的函数,如果对于所有参数i
,ICSi(F1)
不是比ICSi(F2)
更糟糕的转换序列,则
- […这个例子中有很多不相关的案例…]或者,如果不是那样,那么
- F2是重写的候选者([over.match.oper]),而F1不是
- F1和F2是重写候选,F2是参数顺序相反的合成候选,F1不是
#2
和#3
是重写候选项,#3
的参数顺序相反,而#1
不重写。但是,为了达到这一点,我们需要首先通过这个初始条件:对于所有的参数,转换序列并不更差
#1
优于#2
,因为所有的转换序列都是相同的(很小,因为函数参数是相同的),并且#2
是重写候选,而#1
不是
但是。。。在第一种情况下,两对#1
/#3
和#2
/#3
卡滞。在这两种情况下,第一个参数对于#1
/#2
具有更好的转换顺序,而第二个参数对于#3
具有更好的转换顺序(即常量的参数必须经过额外的常量
限定,因此其转换顺序更差)。这种const
触发器使我们无法选择其中一种
因此,整个重载解决方案是不明确的
据我所知,这只适用于返回类型为bool
的情况
那是不对的。我们无条件地考虑改写和颠倒的候选人。我们的规则是:
如果重写的运算符==
候选者是通过运算符@
的重载解析选择的,则其返回类型应为cvbool
P:那就是,我们仍然考虑这些候选人。但是,如果最佳可行的候选者是一个返回的操作符==
,比如说,Meta
,那么结果基本上与该候选者被删除时相同
我们不希望处于过载解决方案必须考虑返回类型的状态。在任何情况下,这里的代码返回Meta
这一事实都是无关紧要的——如果它返回bool
,问题也会存在
谢天谢地,这里的修复很容易:
struct Foo {
Meta operator==(const Foo&) const;
Meta operator!=(const Foo&) const;
// ^^^^^^
};
一旦您使用两个比较运算符const
,就不再有歧义了。所有的参数都是相同的,因此所有的转换序列基本上是相同的<代码>#1
现在通过不重写而击败#3
,#2
现在通过不反转而击败#3
,这使得#1
成为最佳可行的候选。与我们在C++17中得到的结果相同,只需再多走几步即可实现。本征问题似乎归结为以下几点:
使用标量=double;
模板
结构基{
友元内联整数运算符==(常量标量&,常量派生&){return 1;}
int运算符!=(常量标量&)常量;
};
结构X:Base{};
int main(){
X{}!=0.0;
}
该表达式的两个候选项是
从运算符==(常量标量&,常量派生&)重写的候选项
Base::operator=(常数标量和)常数
根据,作为操作员=代码>未通过using声明导入到X
的范围中,#2的隐式对象参数的类型为const Base&
。因此,#1对于该参数具有更好的隐式转换序列(精确匹配,而不是派生到基转换)。选择#1会导致程序格式错误
可能的修复方法:
- 使用Base::operator!=添加
代码>到衍生的
,或
- 将
运算符==
更改为采用常量基&
而不是常量派生&
我们的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);
}
模板类gpu\u类型;
乌辛
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
struct Foo {
Meta operator==(const Foo&) const;
Meta operator!=(const Foo&) const;
// ^^^^^^
};
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);
};