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