10

Please consider the following example with an aggregate struct B with the field of type U. The field's destructor is private, but available to the aggregate due to friend declaration, and not available for calling from main function:

class U { 
    ~U() {}
    friend struct B;
};

struct B { U v{}; };

int main()
{
    B b; //ok everywhere
    auto pb = new B; //ok everywhere
    delete pb;
    pb = new B{}; //ok in GCC, error in Clang
    delete pb;
}

And if one uses aggregate initialization B{} then the code is accepted only by GCC, while Clang reports the error:

error: temporary of type 'U' has private destructor
    pb = new B{}; //ok in GCC, error in Clang
               ^
<source>:2:5: note: implicitly declared private here
    ~U() {}
    ^

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

I have not found any mentions of the "destructor" in https://en.cppreference.com/w/cpp/language/aggregate_initialization . Is it really required by the standard for the aggregate field to have its destructor available to every potential user of the aggregate?

R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • clang 7.1.0 can compile this. clang 8.0.0+ can not. – 273K Aug 06 '21 at 17:09
  • I do not understand why this code creates a temporary at all. There is a matching question from yesterday: https://stackoverflow.com/questions/68672933/why-does-default-initialization-of-a-class-field-in-c-require-destructor-invoc – SergeyA Aug 06 '21 at 17:15
  • Presumably the idea is that if a field constructor throws, then already constructed fields have to be destroyed (and a corner case of 1 field never having to be destroyed in this manner didn't get special handling). Since aggregate init is not a constructor call, it makes some sense that access is cheched for the caller and not for the aggregate. – HolyBlackCat Aug 06 '21 at 17:33
  • 1
    Clang rejects `B b{};` too for the same reason. "_temporary of type 'U' has private destructor_" – Ted Lyngmo Aug 06 '21 at 17:36

1 Answers1

7

Access to the destructor is indeed required by the standard. Quoting from N4868 (closest to the published C++20), [dcl.init.aggr]/8 says:

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 ]

For completeness, [class.dtor]/15 says:

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


[dcl.init.aggr]/8 was added by the resolution of DR2227, first published in C++20. It's a defect report, so compilers should apply it to previous standard versions as well.

bogdan
  • 9,229
  • 2
  • 33
  • 48