c++constructorlanguage-lawyerincomplete-type

Difference between constructor definitions of a class holding a std::unique_ptr to an incomplete type


The code below compiles OK (see Golbolt link below):

#include <memory>

struct B;

struct A1 {
    A1() = default;
    ~A1();
    std::unique_ptr<B> ptr;
};

#if 0
struct A2 {
    A2();
    ~A2();
    std::unique_ptr<B> ptr;
};
A2::A2() = default;
#endif

int main()
{
}

But if I replace #if 0 with #if 1 to compile class A2 I get the following error from gcc:

In file included from /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/memory:76,
                 from <source>:1:
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/unique_ptr.h: In instantiation of 'void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = B]':
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/unique_ptr.h:396:17:   required from 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = B; _Dp = std::default_delete<B>]'
<source>:17:1:   required from here
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/unique_ptr.h:93:23: error: invalid application of 'sizeof' to incomplete type 'B'
   93 |         static_assert(sizeof(_Tp)>0,
      |                       ^~~~~~~~~~~
ASM generation compiler returned: 1
In file included from /opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/memory:76,
                 from <source>:1:
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/unique_ptr.h: In instantiation of 'void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = B]':
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/unique_ptr.h:396:17:   required from 'std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = B; _Dp = std::default_delete<B>]'
<source>:17:1:   required from here
/opt/compiler-explorer/gcc-12.2.0/include/c++/12.2.0/bits/unique_ptr.h:93:23: error: invalid application of 'sizeof' to incomplete type 'B'
   93 |         static_assert(sizeof(_Tp)>0,
      |                       ^~~~~~~~~~~
Execution build compiler returned: 1

Godbolt

I get similar result on MSVC.
I also get this result whether I compile as C++17 or C++20.

My question:
The only difference between A1 and A2 is the definition of the constructor inside the class definition or out of it (in both cases it's defined as default).
Why is there a difference in this case ?

This question is a follow up on this post: Why unique_ptr requires complete type in constructor?


Solution

  • Here's the difference: in A1, where the default constructor is defaulted on its first declaration, the compiler does not actually define it until its definition is needed, and since at no point do you actually attempt to create an A1 object, the default constructor's definition is never needed in this translation unit, so the compiler never generates the definition. As for A2, the out-of-line definition

    A2::A2() = default;
    

    actually generates a definition for the function where it occurs. At that point, B must be complete but it is not. This is explained in the linked question.

    See [dcl.fct.def.default]/5:

    [...] A function is user-provided if it is user-declared and not explicitly defaulted or deleted on its first declaration. A user-provided explicitly-defaulted function (i.e., explicitly defaulted after its first declaration) is implicitly defined at the point where it is explicitly defaulted; if such a function is implicitly defined as deleted, the program is ill-formed. A non-user-provided defaulted function (i.e. implicitly declared or explicitly defaulted in the class) that is not defined as deleted is implicitly defined when it is odr-used ([basic.def.odr]) or needed for constant evaluation ([expr.const]).