Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler differences in template expansion with default member value and incomplete type

There appears to be a difference in when templates are expanded for class members that have default initializers between MSVC and Clang, which can sometimes lead to code that compiles successfully in MSVC but fails in Clang.

The problem code in question was fairly complex and spread across multiple files, but I think the following toy example shows the same discrepancy:

#include <memory>

class Impl;

class A {
  std::unique_ptr<Impl> ptr = nullptr;
public:
  A();
  ~A();
};

int main() {}

https://godbolt.org/z/3s5Drh

As seen in the compiler explorer, Clang gives an error for this code. If the = nullptr is removed, both compilers will run without errors.

Obviously this code won't do anything and even if it did, the = nullptr would not be necessary anyway. I am curious though, as to whether there is anything in the standard that says whether one or other of these compilers is correct in this case?

like image 701
Greg Avatar asked Apr 16 '19 14:04

Greg


1 Answers

There are several bugs involved here, see https://bugs.llvm.org/show_bug.cgi?id=39363#c8

So: two bugs in GCC (though they might be the same thing), one bug in Clang's C++17 support, one bug in libstdc++, and no bugs in libc++. Retargeting this as a Clang bug. :)

And the libstdc++ was actually a defect in the standard that got inadvertently fixed by https://wg21.link/lwg2081 (see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87704 for more details).

I think the program is actually invalid in C++14 mode, because the default member initializer uses copy-initialization, so a temporary gets created and then moved-from, and then the temporary destroyed. Destroying the temporary means the destructor should be instantiated, which requires a complete type. In C++17 mode guaranteed copy elision means there is no temporary, and so no destructor instantiation, and the code should be valid. But GCC and Clang both do the wrong thing, even in C++17 mode.

If you use direct-list-initialization instead of copy-initialization then it works with Clang:

std::unique_ptr<Impl> ptr{nullptr};

And this also works:

std::unique_ptr<Impl> ptr{};

And the equivalent:

std::unique_ptr<Impl> ptr = {};

And just not providing an initializer at all also works, and works with GCC too:

std::unique_ptr<Impl> ptr;
like image 118
Jonathan Wakely Avatar answered Nov 15 '22 06:11

Jonathan Wakely