Различия компилятора в расширении шаблона со значением элемента по умолчанию и неполным типом - PullRequest
8 голосов
/ 16 апреля 2019

Похоже, есть разница в том, когда шаблоны раскрываются для членов класса, которые имеют инициализаторы по умолчанию между MSVC и Clang, что иногда может привести к коду, который успешно компилируется в MSVC, но не выполняется в Clang.

Код проблемы, о котором идет речь, был довольно сложным и разбросан по нескольким файлам, но я думаю, что следующий игрушечный пример демонстрирует такое же расхождение:

#include <memory>

class Impl;

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

int main() {}

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

Как видно из проводника компилятора, Clang выдает ошибку для этого кода. Если удалить = nullptr, оба компилятора будут работать без ошибок.

Очевидно, что этот код ничего не будет делать, и даже если бы он это сделал, = nullptr не был бы необходим в любом случае. Мне любопытно, что в стандарте есть что-то, что говорит о правильности одного или другого из этих компиляторов в этом случае?

1 Ответ

7 голосов
/ 16 апреля 2019

Здесь задействовано несколько ошибок, см. https://bugs.llvm.org/show_bug.cgi?id=39363#c8

Итак: две ошибки в GCC (хотя они могут быть одинаковыми), одна ошибка в поддержке Clang C ++ 17, однаошибка в libstdc ++, и никаких ошибок в libc ++.Ретаргетинг это как ошибка Clang.:)

И libstdc ++ фактически был дефектом в стандарте, который был непреднамеренно исправлен с помощью https://wg21.link/lwg2081 (подробности см. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87704).

Я думаю, что программа в действительности недопустима в режиме C ++ 14, потому что инициализатор члена по умолчанию использует инициализацию копирования, поэтому создается временный объект, затем перемещается из него, а затем временный уничтожается.Уничтожение временного означает, что деструктор должен быть создан, что требует полного типа.В режиме C ++ 17 гарантированное исключение копирования означает, что временного нет, и, следовательно, нет экземпляра деструктора, и код должен быть действительным.Но GCC и Clang делают неправильно, даже в режиме C ++ 17.

Если вы используете прямую инициализацию списка вместо инициализации копирования, тогда она работает с Clang:

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

И это также работает:

std::unique_ptr<Impl> ptr{};

И эквивалент:

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

И просто не предоставляя инициализатор вообще также работает, и работает с GCC тоже:

std::unique_ptr<Impl> ptr;
...