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

2

9

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?

Harhay answered 7/6, 2019 at 16:0 Comment(4)
(slightly) simpler version with the same behavior: godbolt.org/z/VUBXqd Based on that, I tend to think it's a bug, since the same thing without explicit default initialization compiles OK. (B b{} fails, while B b succeeds)Ripon
Related: reviews.llvm.org/D53860Sacellum
Is final important? EDIT: it appears that it isn'tSmalley
Clang should be correct. This is due to aggregate. Try auto b =B(); see if it compiles. is a dup to #56367980Cutwork
O
1

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.

Ocher answered 7/8, 2021 at 11:35 Comment(3)
N4868 is a better approximation to C++20, partly because of subclause reorganization.Vinous
I think the question is the same in C++17 and C++20. At least the compilers issue the same errors in both modes. But in C++20 the standard clarifies better this behavior.Ocher
Sure—I wasn’t claiming anything was different, just trying to get people to use the best “C++20” as a reference.Vinous
W
2

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?

Worcester answered 7/6, 2019 at 16:59 Comment(7)
This does not address the issue, which is more complex and involves the rules surrounding guaranteed copy elision. It's not about coding style conventions. Furthermore, a wiki is not the standard.Conjecture
@LightnessRacesinOrbit The guideline confirms that deriving from a class that has a protected dtor is allowed, which was what oblivion (and I) understood that Q was.Smalley
@curiousguy, @oblivion I intended the question not as whether deriving from a class with a protected destructor is generally allowed, but whether this particular example is allowed, and the compilers disagree. The comments suggest that it should not be allowed, and that clang is correct to reject it. Also, B{} is aggregate initialization here, not value initialization.Harhay
@JeffGarrett For many ppl it isn't clear at first what could even be ill formed in the example, and how the aggregate init syntax matters! The Q doesn't even say "aggregate" (not does the error msg).Smalley
Oh yes for sure. It seems counterintuitive. I probably should've said aggregate, but I wasn't sure I wasn't missing something. Initialization in C++ is complex. :)Harhay
@JeffGarrett I updated the answer. Seems the standard clearly mentioned you cannot have protected dtor.Worcester
cppreference isn't "the standard."Readjustment
O
1

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.

Ocher answered 7/8, 2021 at 11:35 Comment(3)
N4868 is a better approximation to C++20, partly because of subclause reorganization.Vinous
I think the question is the same in C++17 and C++20. At least the compilers issue the same errors in both modes. But in C++20 the standard clarifies better this behavior.Ocher
Sure—I wasn’t claiming anything was different, just trying to get people to use the best “C++20” as a reference.Vinous

© 2022 - 2024 — McMap. All rights reserved.