9

Please help me with this program:

struct U { 
    U(int *) noexcept;
private:
    ~U() noexcept;
};

struct B {
    B();
    ~B();
    U v; //ok
    U w{nullptr}; //ok
    U u = nullptr; //error
};

Here struct U has a private destructor only for demonstration that the destructor is not really invoked by the compiler and to simplify the length of the code.

And struct B has only declared a default constructor and destructor, so the compiler will not generate them in this translation unit.

Also struct B has 3 fields: v, w and u. There is no problem with the v and w fields, but for field u the compiler issues an error about an inaccessible destructor for U:

error: 'U::~U()' is private within this context
   13 |     U u = nullptr; //error

Demo: https://gcc.godbolt.org/z/YooGe9xq6

The questions are:

  1. If B::B() is not compiled in this translation unit, why is field default initialization considered at all?
  2. Is the error because a temporary object is created for the initialization of the u field? (No mandatory copy elision?)
  3. What is the difference between the u and w cases then?
Boann
  • 48,794
  • 16
  • 117
  • 146
  • Note that mandatory [copy elision](https://en.cppreference.com/w/cpp/language/copy_elision) cannot take place because it requires the destructor to be accessible at the point the copy would take place. See [this question](https://stackoverflow.com/questions/68657541/why-is-public-destructor-necessary-for-mandatory-rvo-in-c/) for some information about why this is the case. – Nathan Pierson Aug 05 '21 at 20:16
  • I am willing to attribute this diagnostic to compiler idiosyncrasy. For example, both `icc` and `msvc` refuse to compile the code even without offending line. Overall, `B` is unusable no matter how `U` member is initialized. – SergeyA Aug 05 '21 at 20:18

1 Answers1

5

If B::B() is not compiled in this translation unit, why field default initialization is considered at all?

Because member initialization is part of the class' definition. So it's the class definition itself that's invalid, without having to involve the definition of its constructor(s). In the standard, you can start from [class.mem.general] and follow through brace-or-equal-initializer, which eventually requires a valid "bog-standard" assignment-expression.

Is the error because of a temporary object created for the initialization of u field? (No mandatory copy elision?)

Yes, the compiler error in your godbolt link is clear about that:

<source>:13:11: error: temporary of type 'U' has private destructor
    U u = nullptr; //error

see Why is public destructor necessary for mandatory RVO in C++? for an explanation on why mandatory copy-elision doesn't apply (Thanks @NathanPierson!)

What is the difference between u and w cases then?

w is initialized directly without creating a temporary beforehand so creating it works fine because nothing in the expression ever tries to invoke the destructor of U. Obviously any definition of B::~B() would still fail on w, but that's beyond the point.

  • Footnote: I don't feel confident enough to make this part of the answer, but as far as I can tell, the reason why the equal-initializer must be a full-fledged valid expression during the definition of the class is the good-old "C++ can't be parsed out of context" conundrum. And adding specific contextual exceptions wrt/ protection levels and other stuff would become a mess real quick. Someone please tell me if this is off-base. –  Aug 05 '21 at 20:53