【问题标题】:Conversion operator with const-result - GCC/Clang discrepancy具有 const-result 的转换运算符 - GCC/Clang 差异
【发布时间】:2022-01-10 13:44:21
【问题描述】:

给定以下代码sn-p:

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 或返回 const 引用来解决。

【问题讨论】:

  • 仅在 GCC、Clang 和 MSVC 之外 Clang 会产生此错误,仅用于直接初始化(不是复制初始化或引用初始化)并且仅在 C++14 或更低模式下。
  • 此外,只要为 foo 显式声明了复制构造函数,它就可以很好地编译:godbolt.org/z/fz8KE11ns
  • 在 clang 12 上将 C++ 标准更改为 17 及更高版本允许它工作。 gcc.godbolt.org/z/KhGxzo6xs
  • @alagner 声明复制构造函数禁止声明隐式移动构造函数,这是 Clang 似乎试图在构造中使用的。也许这是CWG 2077
  • 这很有趣:godbolt.org/z/YM15T5Gc3 只有在选择 C++11 或 C++14 时才会在 clang 上失败。即使对于 C++03 也很好。

标签: c++ language-lawyer


【解决方案1】:

我认为这是公开的CWG issue 2077

基本上,

Foo foo(bar);

是直接初始化,这意味着它将考虑Foo 的构造函数进行重载解析并选择最佳可行的构造函数。候选者是带有签名的隐式复制和移动构造函数

Foo(const Foo&);
Foo(Foo&&);

如果您查看链接的 CWG 问题并了解更多详细信息 related Clang bug,那么我们看到这正是那里描述的情况,只是我们的函数调用是构造函数调用,而他们的函数调用是正常的函数调用。

如果我们只是相信 CWG 问题中提出的论点,那么根据当前规则,重载解析显然应该选择 Foo&amp;&amp; 重载,而实际上不允许绑定引用,从而导致程序格式错误。


我将尝试从此处的链接来源重现推理:

考虑从bar转换为构造函数的参数时,由于类型不匹配,引用不能直接绑定bar

根据[over.ics.ref]/2,在这种情况下,转换顺序就像通过复制初始化引用类型的临时对象一样确定。

在这种情况下,转换是Bar -&gt; const Foo -&gt; Foo,通过移动构造函数的用户定义构造函数。 对于复制构造函数,它是Bar -&gt; const Foo

但是,在临时的复制初始化中,常量限定符转换被“包含”在初始化中。 [over.ics.ref]/2 所以移动构造函数的顺序并不比复制构造函数差。

在这种情况下,右值引用是一个决胜局,因此在重载决议中选择了移动构造函数。

但是,const Foo 不能绑定到非const 右值引用。因此,该程序是格式错误的。 ([dcl.init.ref]/5.4.3, DR 1604)

还请注意,[over.ics.ref] 的其余部分提供了对引用绑定的要求,以影响重载的可行性,但不包括将const 右值绑定到非const 右值引用。


这不是问题,例如复制初始化(Foo foo = bar;),因为这样可以直接使用转换操作符而无需经过构造函数。

在 C++11 之前也不是问题,因为那里不存在移动构造函数。

出于同样的原因,如果 Foo 的复制构造函数被显式声明也不是问题,因为这会抑制隐式移动构造函数的声明。

这在 C++17 及更高版本中也不是问题,因为强制复制省略使得它永远不会被调用移动构造函数。

我认为这解释了 Clang 的所有行为。


GCC 和 MSVC 似乎在重载决议中选择了复制构造函数而不是移动构造函数,虽然按照标准的当前措辞显然不正确,但可能是打算发生的事情以及链接的 CWG 问题的目的,通过从一组可行的函数中删除带有格式错误的引用绑定的函数重载。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-03-02
    • 1970-01-01
    • 2016-09-15
    • 2011-01-20
    • 2017-02-22
    • 1970-01-01
    • 1970-01-01
    • 2017-10-09
    相关资源
    最近更新 更多