【问题标题】:Why is this direct initialization valid? (C++ 17)为什么这种直接初始化有效? (C++ 17)
【发布时间】:2018-10-26 03:07:22
【问题描述】:

考虑以下两个类:

class B
{
public:
   B() { }
   B(const B& b) = delete; //Move ctor not implicitly declared
};


class A
{
public:
   A() { }

   operator B()
   {
       return B();
   }
};

我明白为什么这段代码编译得很好:

A a;
B b = a;

遵循copy-initialization 的规则,对象“a”被转换为类型 B 的纯右值,并且由于在 C++17 中不再需要复制构造函数,因此没有错误:

如果 T 是一个类类型,并且该类型的 cv 非限定版本 other 不是 T 或派生自 T,或者如果 T 是非类类型,但 type of other 是类类型,用户定义的转换序列 可以从 other 的类型转换为 T(或从 T 派生的类型 如果 T 是类类型并且转换函数可用)是 检查并通过重载决议选择最好的。这 转换的结果,它是一个临时纯右值(直到 C++17)prvalue 表达式 (C++17 起) 如果转换构造函数是 使用,然后用于直接初始化对象。最后一步是 通常优化出来并构造转换的结果 直接在为目标对象分配的内存中,但是 需要适当的构造函数(移动或复制)才能访问 即使它没有被使用。 (直到 C++17)

但是为什么这种直接的列表初始化也会编译呢?

A a;
B b{ a };

我在list-initialization 中找不到任何措辞,说明在这种情况下编译器应尝试将 A 转换为 B。只考虑构造函数的重载决议:

如果前一个阶段没有产生匹配,T的所有构造函数 参与针对一组参数的重载决议 由支撑初始化列表的元素组成,具有限制 只允许非缩小转换

但是在这种情况下复制构造函数被删除了,所以它不应该被重载决议选择吗?

【问题讨论】:

  • 要明确,当复制只能通过引用复制语义完成时,不需要复制构造函数,仍然需要深度复制对象。
  • 代码B b{ a.operator B() }; 会很好:从相同类型的纯右值初始化是通过临时实现到b。 (dcl.init/17.6.1)。但是在B b{a}; 中,a 不是纯右值,所以我认为该子句不适用,因此应该拒绝它(dcl.init/17.6.2 - 应用于构造函数的重载决议)
  • 复制构造函数没有被使用,我认为编译器已经决定将临时从return B()具体化为b

标签: c++ initialization language-lawyer c++17


【解决方案1】:

这是CWG 2327。就标准而言,您是正确的,但一些编译器还在这种情况下考虑了转换函数 - 因为它真的很有意义。

【讨论】:

  • 确实上面的代码在 gcc 和 clang 上都可以工作,但是被 msvc 拒绝了。我想在这种情况下,使用当前标准应该首选复制初始化。
猜你喜欢
  • 1970-01-01
  • 2010-10-02
  • 2017-02-24
  • 2017-07-01
  • 2020-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多