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
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?
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 anA1object, the default constructor's definition is never needed in this translation unit, so the compiler never generates the definition. As forA2, the out-of-line definitionactually generates a definition for the function where it occurs. At that point,
Bmust be complete but it is not. This is explained in the linked question.See [dcl.fct.def.default]/5: