Does this code result in a materialized base prvalue, and should it compile?

816 Views Asked by At

The following code compiles in gcc 9.1 godbolt but not clang 8 godbolt:

class A {
protected:
    ~A() = default;
};

class B final : public A {
};

int main() {
    auto b = B{};
}

Clang's error:

<source>:10:16: error: temporary of type 'A' has protected destructor
    auto b = B{};
               ^
<source>:3:5: note: declared protected here
    ~A() = default;
    ^

Which is correct and why?

2

There are 2 best solutions below

3
Fedor On BEST ANSWER

Yes, Clang is correct in rejecting the code.

In auto b = B{}; we have an aggregate initialization, which happens directly in main function. So this function must be able to call destructors of B subtypes in case of exception occurrence during the initialization.

A quotation from N4861 (the last C++20 draft), [dcl.init.aggr]/8:

The destructor for each element of class type is potentially invoked from the context where the aggregate initialization occurs. [ Note: This provision ensures that destructors can be called for fully-constructed subobjects in case an exception is thrown. — end note ]

Just to be complete, a quote from [class.dtor]/15:

[...] A program is ill-formed if a destructor that is potentially invoked is deleted or not accessible from the context of the invocation.

7
Oblivion On

Thanks for the clarifications in the comments; Since C++17, B{} is aggregate even though it is derived from A, so a temporary A will be created for the aggregate init by the user which has no access to the dtor. So clang is correct in rejecting the compile. The standard:

no virtual, private, or protected (since C++17) base classes

However using () will work as the standard says.

The dtor of the base can be public or protected.

A common guideline is that a destructor for a base class must be either public and virtual or protected and nonvirtual

see the guideline of standard

In contrast with C++11, where the expression B() is a prvalue, and auto b = B(); is a move-construction and the move will likely get elided, In C++17, there is no move. The prvalue is not moved from. This is value-initializing B() and is exactly equivalent to:

B();

Value Categories in C++17

Should this code fail to compile in C++17?