TransWikia.com

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

Stack Overflow Asked by C.M. on November 4, 2021

Consider 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

Questions:

  • 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)

  • MSVC can’t initialize member variable — does it mean it is non-compliant? Is there a way to workaround this?

  • why adding S(S&&) = delete; causes MSVC to compile every case?

3 Answers

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)

The standard says that it should work. The standard is wrong.

A base class subobject (more generally, a potentially-overlapping subobject) may have a different layout from a complete object of the same type, or may have its padding reused by other objects. It is therefore impossible to elide a copy or move from a function returning a prvalue, since the function has no idea that it is not initializing a complete object.

The rest are MSVC bugs.

Answered by T.C. on November 4, 2021

No, it's not allowed, but since c++17, there are some new features, and one of them, functions no more 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


The following function, never return S() object and return, std::initialization_list or {}, instead, as S s ={}, is a valid conversion, so based on copy elision optimization, it's not going to return a copy and roughly speaking - directly return std::initialization_list itself. Note temporary materialization conversion.

S foo() { return {}; }

The following not going to work,

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

So that, the line Y() : s(foo()) {}, roughly speaking - now could be interpreted as implicit type conversion,

S s = {}

Answered by 4.Pi.n on November 4, 2021

So, I think I found the relevant parts of the standard and I think the compilers are in error regarding to X. (All links are to a standard draft so very maybe it was different in C++17, I will check that later. But gcc10 and clang10 also fail with -std=c++20, so that is not that important).

Regarding the initialization of base classes (emphasis mine): 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.

I think this tells us, that X() : S(foo()) {} should not be different from S s = foo(), but let's look at 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]

This implies to me, that X() : S(foo()) {} should call the default constructor. I also tested (to be completely in line with the example) X() : S(S()) {} and this also fails on clang and g++. So it seems to me that the compilers have a defect.

Answered by n314159 on November 4, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP