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?

curiousguy
  • 8,038
  • 2
  • 40
  • 58
Jeff Garrett
  • 5,863
  • 1
  • 13
  • 12
  • 2
    (slightly) simpler version with the same behavior: https://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) – SergeyA Jun 07 '19 at 16:03
  • 4
    Related: https://reviews.llvm.org/D53860 – Evg Jun 07 '19 at 16:08
  • Is `final` important? EDIT: it appears that it isn't – curiousguy Jun 07 '19 at 17:51
  • Clang should be correct. This is due to aggregate. Try `auto b =B();` see if it compiles. is a dup to https://stackoverflow.com/questions/56367480/should-this-code-fail-to-compile-in-c17 – Hui Jun 07 '19 at 20:18

2 Answers2

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?

Oblivion
  • 7,176
  • 2
  • 14
  • 33
  • 1
    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. – Lightness Races in Orbit Jun 07 '19 at 17:44
  • 1
    @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. – curiousguy Jun 07 '19 at 17:57
  • @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. – Jeff Garrett Jun 07 '19 at 21:02
  • @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). – curiousguy Jun 07 '19 at 21:06
  • 1
    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. :) – Jeff Garrett Jun 07 '19 at 21:58
  • @JeffGarrett I updated the answer. Seems the standard clearly mentioned you cannot have protected dtor. – Oblivion Jun 07 '19 at 23:06
  • 2
    cppreference isn't "the standard." – L. F. Jun 08 '19 at 01:40
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.

Fedor
  • 17,146
  • 13
  • 40
  • 131
  • 1
    N4868 is a better approximation to C++20, partly because of subclause reorganization. – Davis Herring Aug 07 '21 at 12:45
  • 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. – Fedor Aug 07 '21 at 12:47
  • 2
    Sure—I wasn’t claiming anything was different, just trying to get people to use the best “C++20” as a reference. – Davis Herring Aug 07 '21 at 12:50