Please consider a struct A
having a field u
of type U<R>
with a default initializer. The destructor ~U<R>
is only declared:
template<typename T>
struct U {
~U();
};
struct R;
struct A {
U<R> u = U<R>{};
};
All compilers accept this code, demo: https://gcc.godbolt.org/z/oqMjTovMo
But if we define the destructor ~U<R>
as follows:
template<typename T>
struct U {
~U() { static_assert( sizeof(T) > 0 ); }
};
then the current compilers diverge. MSVC keeps accepting the program, while GCC/Clang print the error
error: invalid application of 'sizeof' to an incomplete type 'R'
demo: https://gcc.godbolt.org/z/713TzPd6v
Obviously, the compiler must verify destructor availability of default initialing class members in case an exception occurs during construction. But does the standard require that the compiler just check the availability of the destructor (as MSVC does), or the compiler should verify its body as well? MSVC behavior looks more convenient here since it permits forward declaration of R
by the moment of struct A
definition.
P. S. This questing has not only purely theoretical interest. If one replaces U
here with std::unique_ptr
then it explains why class fields of type std::unique_ptr<R>
are accepted by MSVC and rejected by GCC/Clang for incomplete classes R
.
The default destructor calls the destructors of the base class and members of the derived class. The destructors of base classes and members are called in the reverse order of the completion of their constructor: The destructor for a class object is called before destructors for members and bases are called.
A destructor is a member function that is invoked automatically when the object goes out of scope or is explicitly destroyed by a call to delete . A destructor has the same name as the class, preceded by a tilde ( ~ ). For example, the destructor for class String is declared: ~String() .
The destructor does not have arguments. It has no return type not even void. An object of a class with a Destructor cannot become a member of the union. A destructor should be declared in the public section of the class.
Implicitly-declared destructor If no user-declared prospective (since C++20) destructor is provided for a class type (struct, class, or union), the compiler will always declare a destructor as an inline public member of its class.
[dcl.init.aggr]/8: (emphasis mine)
The destructor for each element of class type is potentially invoked ([class.dtor]) from the context where the aggregate initialization occurs.
[basic.def.odr]/8:
A destructor for a class is odr-used if it is potentially invoked.
[basic.def.odr]/10:
Every program shall contain exactly one definition of every non-inline function or variable that is odr-used in that program outside of a discarded statement; no diagnostic required.
So ~U()
is potentially invoked at U<R> u
inside A
, which requires its definition.
Then according to [temp.inst]/4:
Unless a member of a class template or a member template is a declared specialization, the specialization of the member is implicitly instantiated when the specialization is referenced in a context that requires the member definition to exist ...
Bonus note: and of course, it is actually invoked whenever an instance of A
is destroyed. So it needs to be compiled.
Note: in the first version the compilers accept the code because the destructor is not inline, so it fails during linking (example).
As for MSVC accepting the code, it appears to be a bug.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With