Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Must destructor be just available (public) or fully valid for default initialized class members?

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.

like image 894
Fedor Avatar asked Oct 15 '22 20:10

Fedor


People also ask

What is the default destructor in C++?

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.

How to invoke destructor in c++?

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() .

Can destructors take arguments?

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.

Does struct have destructor C++?

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.


1 Answers

[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.

like image 150
rustyx Avatar answered Oct 23 '22 04:10

rustyx