Reputation: 122516
This is a follow up of this question: Does PIMPL idiom actually work using std::unique_ptr?
The full example uses multiple files, so for the sake of this question I will reduce it here. The full working example is here: https://wandbox.org/permlink/AepAJYkbRU4buDoJ and the full non-working example here: https://wandbox.org/permlink/0kP23UYJbSaUvJgS.
The shorter example is:
#include <memory>
struct A;
struct B {
~B();
std::unique_ptr<A> p = nullptr;
};
Which produces the error:
In file included from <source>:1:
In file included from /opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../include/c++/11.2.0/memory:76:
/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../include/c++/11.2.0/bits/unique_ptr.h:83:16: error: invalid application of 'sizeof' to an incomplete type 'A'
static_assert(sizeof(_Tp)>0,
^~~~~~~~~~~
/opt/compiler-explorer/gcc-11.2.0/lib/gcc/x86_64-linux-gnu/11.2.0/../../../../include/c++/11.2.0/bits/unique_ptr.h:361:4: note: in instantiation of member function 'std::default_delete<A>::operator()' requested here
get_deleter()(std::move(__ptr));
^
<source>:7:29: note: in instantiation of member function 'std::unique_ptr<A>::~unique_ptr' requested here
std::unique_ptr<A> p = nullptr;
^
<source>:3:8: note: forward declaration of 'A'
struct A;
^
1 error generated.
While using {}
compiles:
#include <memory>
struct A;
struct B {
~B();
std::unique_ptr<A> p{nullptr};
};
No errors: https://godbolt.org/z/snsfsjdqE
What is the difference? Why does std::unique_ptr<A> p = nullptr;
require to instantiate the unique_ptr
destructor, but std::unique_ptr<A> p{nullptr};
does not?
PS: I used clang for a clearer error message. Same results with gcc. All versions I tried so far.
Upvotes: 16
Views: 624
Reputation: 13760
It seems to be a language issue unrelated to std::unique_ptr
.
I would say a bug in both GCC and Clang.
Here it is without std::unique_ptr
.
This one compiles:
template<typename T>
struct uptr {
uptr(nullptr_t) {}
~uptr() {
delete (new T);
}
};
class A
{
public:
A();
~A();
private:
class B;
uptr<B> m_b {nullptr}; // the dtor of uptr is not instantiated yet
};
But this doesn't compile:
template<typename T>
struct uptr {
uptr(nullptr_t) {}
~uptr() {
delete (new T);
}
};
class A
{
public:
A();
~A();
private:
class B;
uptr<B> m_b = nullptr; // the dtor of uptr tries to be instantiated here
};
My intuition is that the instantiation of uptr's destructor shall be delayed to the instantiation of A's destructor, in both cases. So both cases shall pass compilation. This is why I believe it is a bug in the compiler, trying to instantiate the destructor too early, before A's destructor appears and when B is still incomplete, in the second case.
It is interesting to note that with the curly brackets most compilers (GCC, Clang, MSVC) indeed delay the instantiation of the destructor. Only ICC in my check rejects the code - wrongly, I would say. However, if constructing with assignment sign most compilers (GCC, Clang, ICC) instantiate the destructor too early, only MSVC in my check still delays it - as it should, I believe. In case we use both the curly brackets and assignment sign, Clang would join MSVC, delaying the destructor instantiation. GCC and ICC would still try to instantiate it.
See also a related SO post that explains why I believe GCC and Clang are wrong here.
Upvotes: 1