16

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • 2
    [Related](https://stackoverflow.com/questions/58375922/compile-error-with-gcc-when-in-class-initializing-unique-ptr-of-incomplete-type) – Cory Kramer Mar 08 '22 at 15:31
  • @CoryKramer looks like a duplicate, though the answer states that newer versions of gcc are fine with it. I tested with clang 13 and gcc 11.2 – 463035818_is_not_an_ai Mar 08 '22 at 15:36
  • @CoryKramer also not ok with gcc 9.2 https://godbolt.org/z/nej89vGev :/ – 463035818_is_not_an_ai Mar 08 '22 at 15:37
  • Actually, the way I read the comments to that answer that Kramer linked, it was said to be fixed in gcc 9.2 only because the test confirming that was not correct (due to limitations, at that time of the online compiler used). – davidbak Mar 08 '22 at 15:39
  • @davidbak oh ok, when `A` is complete then there is no issue to begin with. Then it isnt a good duplicate – 463035818_is_not_an_ai Mar 08 '22 at 15:40
  • MSVC (latest) compiles it fine. Clang and GCC do not. – Mike Vine Mar 08 '22 at 16:18
  • Which C++ standard are you specifying your compilers use when testing? – Chris Mar 08 '22 at 17:06
  • Does this answer your question? [Why does copy initializaton require destructor in C++17 with guaranteed move/copy elision?](https://stackoverflow.com/questions/65286451/why-does-copy-initializaton-require-destructor-in-c17-with-guaranteed-move-cop) – Artyer Mar 08 '22 at 22:46

1 Answers1

1

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.

Amir Kirsh
  • 12,564
  • 41
  • 74