是否可以初始化不可复制类型的成员变量(或基class)?

Is it possible to initialize member variable (or base class) of a non-copyable type?

考虑 following code:

struct S
{
    S() = default;
    S(S const&) = delete;
    // S(S&&) = delete;  // <--- uncomment for a mind-blowing effect:
                         // MSVC starts compiling EVERY case O_O
};

S foo() { return {}; }

struct X : S
{
//    X() : S(foo()) {}   // <----- all compilers fail here
};

struct Y
{
    S s;
    Y() : s(foo()) {}   // <----- only MSVC fails here
};

struct Z
{
    S s = {};           // ... and yet this is fine with every compiler
    Z() {}
};

//S s1(foo());      // <-- only MSVC fails here
//S s2 = foo();     // <-- only MSVC fails here

问题:

所以,我想我找到了标准的相关部分,我认为编译器在 X 方面是错误的。 (所有链接都指向一个标准草案,所以很可能它在 C++17 中有所不同,我稍后会检查。但是 gcc10 和 clang10 也因 -std=c++20 而失败,所以这并不重要)。

关于基类的初始化(强调我的):class.base.init/7

The expression-list or braced-init-list in a mem-initializer is used to initialize the designated subobject (or, in the case of a delegating constructor, the complete class object) according to the initialization rules of [dcl.init] for direct-initialization.

我认为这告诉我们,X() : S(foo()) {} 应该与 S s = foo() 没有什么不同,但让我们看看 dcl.init/17.6.1

If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. [Example: T x = T(T(T())); calls the T default constructor to initialize x. — end example]

这对我来说意味着 X() : S(foo()) {} 应该调用默认构造函数。我还测试了(完全符合示例)X() : S(S()) {},这在 clang 和 g++ 上也失败了。所以在我看来,编译器有缺陷。

不,这是不允许的,但是自从 c++17 以来,有一些新功能,其中之一不再起作用 copy object

Functions returning prvalues no longer copy objects (mandatory copy elision), and there is a new prvalue-to-glvalue conversion called temporary materialization conversion. This change means that copy elision is now guaranteed, and even applies to types that are not copyable or movable. This allows you to define functions that return such types.

Guaranteed copy elision C++17


下面的函数,从来没有returnS()对象和return、std::initialization_list{},而是S s ={},是有效转换,所以根据复制省略优化,它不是 去 return 一个副本和 粗略地说 - 直接 return std::initialization_list 本身。注意 临时实现转换

S foo() { return {}; }

下面的不行,

S foo() { S s = {}; return s; }

因此,Y() : s(foo()) {}粗略地说 - 现在可以解释为隐式类型转换

S s = {}

It looks like there is no way to initialize non-copyable base class with a prvalue -- is this correct? Looks like a deficiency in standard (or all compilers I tried are non-compliant)

标准说它应该有效。标准不对

基础 class 子对象(更一般地说,可能重叠的子对象)可能与同一类型的完整对象具有不同的布局,或者其填充可能被其他对象重用。因此,不可能从返回纯右值的函数中删除副本或移动,因为该函数不知道它不是在初始化一个完整的对象。

其余为 MSVC 错误。