【问题标题】:Conversion operator vs deleted constructor转换运算符与已删除的构造函数
【发布时间】:2018-06-29 20:19:15
【问题描述】:

请看以下代码:

struct X;

struct Y {
  Y() {}
  Y(X&) = delete;
};

struct X {
  X() {}
  operator Y() {
    return{};
  }
};

int main() {
  X x;
  static_cast<Y>(x);
}

这里,Y 的构造函数采用X 被显式删除,而X 有一个转换运算符到Y。在这两个直接矛盾的地方,似乎=delete总是赢;我在一些最新版本的 GCC、Clang 和 VC++ 上进行了测试。

问题:这是“正确”的行为吗?我认为转换构造函数和转换运算符之间没有特别的优先级,所以上面的代码应该会产生重载解析歧义错误。但事实并非如此。它抱怨使用已删除功能。是因为保证复制省略吗?

我在谷歌上搜索并找到了Conversion constructor vs. conversion operator: precedence。在那个问题中,选择了转换运算符,因为它是一个更好的匹配,因为在转换构造函数中存在const。但是,在我的情况下,将 Y(X&amp;) 替换为 Y(X const&amp;) 没有任何改变。


其实我想要的情况是这样的:

X x;
Y y1(x);                  // Error
Y y2 = static_cast<Y>(x); // OK

是的,有人可能会说这很愚蠢,但确实有一些内置类型的行为就是这样:替换 X &lt;- int&amp;Y &lt;- int&amp;&amp;。无法创建完全模仿内置引用类型的用户定义类型似乎是当前 C++ 中非常缺失的部分......

【问题讨论】:

  • 你不是已经有了想要的情况吗?请注意,您可以直接调用转换运算符,如Y y2 = x.operator Y();

标签: c++ c++11 conversion-operator deleted-functions


【解决方案1】:

问题:这是“正确”的行为吗?我认为转换构造函数和转换运算符之间没有特别的优先级[...]

这不太对。您正在按原样查看代码:

struct Y {
  Y() {}
  Y(X&) = delete;
};

但实际上还有更多的东西。对于编译器,Y 看起来像:

struct Y {
  Y() {}
  Y(X&) = delete;

  Y(Y&&) = default;
  Y(Y const&) = default;
};

这里的选择不在Y(X&amp;)X::operator Y() 之间。选择主要在Y(X&amp;)Y(Y&amp;&amp;)之间。前者比后者更匹配(不管你在问题中提到的,它是X&amp;X const&amp; 作为参数)。但它已被删除,因此转换格式不正确。


如果我们是复制初始化而不是直接初始化:

Y y = x;

那么 两者都是同样可行的(因此是模棱两可的)。是的,你真的希望那是模棱两可的。 = delete 不会从候选集中移除!

将构造函数从 Y(X&amp;) 更改为 Y(X const&amp;) 将首选转换函数。


是的,有人可能会说这很傻,但确实有一些内置类型的行为就是这样:替换 X &lt;- int&amp;, Y &lt;- int&amp;&amp;

是的,但在原始示例中,XY 是不同的类型。在这里,它们代表相同类型的不同值类别。这个新示例工作或不工作的原因完全不同:

X x;
Y y1(x);                  // Error
Y y2 = static_cast<Y>(x); // OK

真的是:

int& x = ...;
int&& y(x);                       // error, can't bind rvalue reference to lvalue
int&& y2 = static_cast<int&&>(x); // ok. this is exactly std::move(x)

与引用兼容类型的引用绑定与转换优先级不同。

【讨论】:

  • 解释的很清楚!谢谢你。所以,既然static_cast&lt;Y&gt;(x)Y y(x) 都是直接初始化的情况,没有办法区分它们,对吧?
【解决方案2】:

来自标准11.6.17.6.2

如果初始化是直接初始化,或者如果它是复制初始化,其中 源类型的 cv 非限定版本与 目的地,构造函数被考虑。

然后标准告诉我们 (11.6.16)

在表单 [...] 以及 new 表达式 (8.5.2.4)、static_cast 表达式 (8.5.1.9)、函数式符号类型转换 (8.5.1.3)、mem 中发生的初始化-initializers (15.6.2),条件的大括号初始化列表形式称为直接初始化。

您的示例通过static_cast 初始化了一个临时变量,因此编译器由于直接初始化而只允许使用构造函数,因此您会收到错误。

【讨论】:

    猜你喜欢
    • 2010-11-25
    • 1970-01-01
    • 2012-09-02
    • 2020-06-12
    • 1970-01-01
    • 2021-12-04
    • 1970-01-01
    • 2014-02-17
    • 2017-03-29
    相关资源
    最近更新 更多