具有 const-result 的转换运算符 - GCC/Clang 差异
Conversion operator with const-result - GCC/Clang discrepancy
给定以下代码片段:
struct Foo {
};
struct Bar {
operator const Foo() {
return Foo();
}
};
int main() {
Bar bar;
Foo foo(bar);
return 0;
}
查看godbolt
它可以用 gcc 11.2 正常编译,但不能用 clang 12.0 编译,并出现以下错误:
<source>:12:13: error: no viable conversion from 'Bar' to 'Foo'
Foo foo(bar);
^~~
<source>:1:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'Bar' to 'const Foo &' for 1st argument
struct Foo {
^
<source>:1:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'Bar' to 'Foo &&' for 1st argument
struct Foo {
^
<source>:5:5: note: candidate function
operator const Foo() {
^
<source>:1:8: note: passing argument to parameter here
struct Foo {
^
- 哪个实现是正确的?
- 这实际上是有效的 C++ 吗?
PS:我知道可以通过删除常量引用的 const
或 return 来修复它。
我认为这是公开的CWG issue 2077。
基本上,
Foo foo(bar);
是直接初始化,意味着它会考虑Foo
的构造函数进行重载决议,并选择最可行的。候选人是带有签名的隐式复制和移动构造函数
Foo(const Foo&);
Foo(Foo&&);
如果您查看链接的 CWG 问题和 related Clang bug 的更多详细信息,那么我们会发现这正是那里描述的情况,只是我们的函数调用是构造函数调用,而他们的是正常的函数调用。
如果我们简单地相信 CWG 问题中提出的论点,那么根据当前规则,重载决议显然应该选择 Foo&&
重载,而实际上不允许绑定引用,从而导致格式错误程序。
我将尝试从此处的链接来源重现推理:
考虑从 bar
到构造函数参数的转换时,由于类型不匹配,引用不能直接绑定 bar
。
根据 [over.ics.ref]/2,在这种情况下,转换序列是由引用类型的临时对象的复制初始化确定的。
在这种情况下,转换是 Bar -> const Foo -> Foo
通过移动构造函数的用户定义构造函数。
对于复制构造函数,它是 Bar -> const Foo
.
但是在临时的复制初始化中,const 限定符转换被“包含”在初始化中。 [over.ics.ref]/2因此移动构造函数顺序并不比拷贝构造函数差
在这种情况下,右值引用是决胜局,因此在重载决议中选择移动构造函数。
但是,const Foo
不能绑定到非 const
右值引用。因此这个程序是病式的。 ([dcl.init.ref]/5.4.3, DR 1604)
另请注意,[over.ics.ref] 的其余部分给出了对引用绑定的要求以影响重载的可行性,但不包括 const
右值与非 [=19= 的绑定] 右值引用。
例如,这不是问题。 copy-initialization (Foo foo = bar;
),因为它可以直接使用转换运算符而不需要通过构造函数。
在 C++11 之前也不是问题,因为那里不存在移动构造函数。
出于同样的原因,如果显式声明 Foo
的复制构造函数也不是问题,因为这会抑制隐式移动构造函数的声明。
这在 C++17 及更高版本中也不是问题,因为强制复制省略使得移动构造函数永远不会被调用。
我认为这可以解释 Clang 的所有行为。
GCC 和 MSVC 似乎在重载决策中选择复制构造函数而不是移动构造函数,虽然从标准的当前措辞来看这显然不正确,但可能是打算发生的事情以及链接的 CWG 问题的目的因为,通过从可行函数集中删除具有格式错误的引用绑定的函数重载。
给定以下代码片段:
struct Foo {
};
struct Bar {
operator const Foo() {
return Foo();
}
};
int main() {
Bar bar;
Foo foo(bar);
return 0;
}
查看godbolt
它可以用 gcc 11.2 正常编译,但不能用 clang 12.0 编译,并出现以下错误:
<source>:12:13: error: no viable conversion from 'Bar' to 'Foo'
Foo foo(bar);
^~~
<source>:1:8: note: candidate constructor (the implicit copy constructor) not viable: no known conversion from 'Bar' to 'const Foo &' for 1st argument
struct Foo {
^
<source>:1:8: note: candidate constructor (the implicit move constructor) not viable: no known conversion from 'Bar' to 'Foo &&' for 1st argument
struct Foo {
^
<source>:5:5: note: candidate function
operator const Foo() {
^
<source>:1:8: note: passing argument to parameter here
struct Foo {
^
- 哪个实现是正确的?
- 这实际上是有效的 C++ 吗?
PS:我知道可以通过删除常量引用的 const
或 return 来修复它。
我认为这是公开的CWG issue 2077。
基本上,
Foo foo(bar);
是直接初始化,意味着它会考虑Foo
的构造函数进行重载决议,并选择最可行的。候选人是带有签名的隐式复制和移动构造函数
Foo(const Foo&);
Foo(Foo&&);
如果您查看链接的 CWG 问题和 related Clang bug 的更多详细信息,那么我们会发现这正是那里描述的情况,只是我们的函数调用是构造函数调用,而他们的是正常的函数调用。
如果我们简单地相信 CWG 问题中提出的论点,那么根据当前规则,重载决议显然应该选择 Foo&&
重载,而实际上不允许绑定引用,从而导致格式错误程序。
我将尝试从此处的链接来源重现推理:
考虑从 bar
到构造函数参数的转换时,由于类型不匹配,引用不能直接绑定 bar
。
根据 [over.ics.ref]/2,在这种情况下,转换序列是由引用类型的临时对象的复制初始化确定的。
在这种情况下,转换是 Bar -> const Foo -> Foo
通过移动构造函数的用户定义构造函数。
对于复制构造函数,它是 Bar -> const Foo
.
但是在临时的复制初始化中,const 限定符转换被“包含”在初始化中。 [over.ics.ref]/2因此移动构造函数顺序并不比拷贝构造函数差
在这种情况下,右值引用是决胜局,因此在重载决议中选择移动构造函数。
但是,const Foo
不能绑定到非 const
右值引用。因此这个程序是病式的。 ([dcl.init.ref]/5.4.3, DR 1604)
另请注意,[over.ics.ref] 的其余部分给出了对引用绑定的要求以影响重载的可行性,但不包括 const
右值与非 [=19= 的绑定] 右值引用。
例如,这不是问题。 copy-initialization (Foo foo = bar;
),因为它可以直接使用转换运算符而不需要通过构造函数。
在 C++11 之前也不是问题,因为那里不存在移动构造函数。
出于同样的原因,如果显式声明 Foo
的复制构造函数也不是问题,因为这会抑制隐式移动构造函数的声明。
这在 C++17 及更高版本中也不是问题,因为强制复制省略使得移动构造函数永远不会被调用。
我认为这可以解释 Clang 的所有行为。
GCC 和 MSVC 似乎在重载决策中选择复制构造函数而不是移动构造函数,虽然从标准的当前措辞来看这显然不正确,但可能是打算发生的事情以及链接的 CWG 问题的目的因为,通过从可行函数集中删除具有格式错误的引用绑定的函数重载。