Различия компилятора в расширении шаблона со значением члена по умолчанию и неполным типом

#c #compiler-errors #language-lawyer

#c #ошибки компилятора #язык-юрист

Вопрос:

По-видимому, существует разница в том, когда шаблоны расширяются для членов класса, у которых есть инициализаторы по умолчанию между 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.Я бы предположил, что это как-то связано с инициализацией (семантически), использующей временный, который нуждается в очистке, который вызывает deleter, который является единственной частью unique_ptr , которая требует Impl полного типа, но то же самое происходит в более новых версиях C , где нет временного (посмотрите на ASTS)… однако gcc соглашается с жалобами clang: godbolt.org/z/vdVdb9

2. Та же ошибка с демонстрацией gcc, но для меня msvc кажется правильным (даже если оба gcc / clang согласны).

3. Мои два цента. выполняется инициализация копирования . В нем говорится, что » соответствующий конструктор (перемещение или копирование) должен быть доступен, даже если он не используется. (до C 17) » Следовательно, в этом случае для копирования / перемещения экземпляра шаблона потребуется деструктор (потому что так std::unique_ptr работает). Однако я не могу дать ответ, потому -std=c 17 что выдает ту же ошибку (она не должна)

4. @BiagioFesta: Код не генерируется, у нас просто есть A определение. Никакие методы не определены, просто объявлены (даже неявно) (игнорирование <memory> 🙂 ).

5. класс Impl должен быть полным типом. Я удивлен, что MSVC этого не уловил.

Ответ №1:

Здесь есть несколько ошибок, см. 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;
 

Комментарии:

1. Комментарий Ричарда Смита объясняет рассуждения более подробно, включая концепцию «потенциально вызываемого», которая здесь уместна.

2. » потому что инициализатор элемента по умолчанию использует инициализацию копирования «. Ну, я подумал то же самое. Однако место, где все должно произойти A::A() , определено в другом TU. Итак, действительно ли нам нужно ожидать ошибки в определении класса (даже если конструктор будет определен в другом TU)?

3. Есть ошибки компилятора, поэтому происходит не то, что вы ожидаете.

4. Я не уверен, что понимаю, почему эта программа будет недействительной в режиме C 14, потому что, как упоминалось выше, я думал, что инициализация копирования будет происходить только в определении конструктора, которого здесь не существует. Я отмечаю это как ответ, потому что сообщения об ошибках действительно объясняют различия компилятора, о которых я спрашивал.