Объявление оператора присваивания по умолчанию как constexpr: какой компилятор прав?

Рассмотреть возможность

struct A1 {
    constexpr A1& operator=(const A1&) = default;
    ~A1() {}
};
struct A2 {
    constexpr A2& operator=(const A2&) = default;
    ~A2() = default;
};
struct A3 {
    ~A3() = default;
    constexpr A3& operator=(const A3&) = default;
};

GCC и MSVC принимают все три структуры. Clang отклоняет A1 и A2 (но принимает A3) со следующим сообщением об ошибке:

<source>:2:5: error: defaulted definition of copy assignment operator is not constexpr
    constexpr A1& operator=(const A1&) = default;
    ^
<source>:6:5: error: defaulted definition of copy assignment operator is not constexpr
    constexpr A2& operator=(const A2&) = default;
    ^
2 errors generated.

(живая демонстрация)

Какой компилятор правильный и почему?

Хороший вопрос. Дальше только предположение... Мы знаем, что constexpr в функциях не означает const. Это означает, может ли эта функция быть вычислена во время компиляции. Оператору присваивания копирования, созданному без вывода сообщений, не предшествует constexpr. Это означает, что constexpr у вас есть перегрузка по сравнению с молча созданным. Однако перегрузка не может быть установлена ​​по умолчанию, что объясняет ошибку. Ознакомьтесь со следующими тремя примерами кода: 1) (clang) rextester.com/WLGFD87794, 2) (gcc) rextester.com/RMWQ86797, 3) (vc++) rextester.com/MXIHQ50551 .

Constantinos Glynos 15.03.2019 13:43
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
35
1
2 174
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Стандарт С++ 17 гласит:

15.8.2 Copy/move assignment operator [class.copy.assign]
...

10 A copy/move assignment operator for a class X that is defaulted and not defined as deleted is implicitly defined when it is odr-used (6.2) (e.g., when it is selected by overload resolution to assign to an object of its class type) or when it is explicitly defaulted after its first declaration. The implicitly-defined copy/move assignment operator is constexpr if
(10.1) — X is a literal type, and
(10.2) — the assignment operator selected to copy/move each direct base class subobject is a constexpr function, and
(10.3) — for each non-static data member of X that is of class type (or array thereof), the assignment operator selected to copy/move that member is a constexpr function.

Оператор копирования-присваивания удовлетворяет вышеуказанным требованиям в двух случаях. В первом случае у нас нелитеральный тип из-за нетривиального деструктора.

Поэтому я считаю, что Clang ошибается, отказываясь от кода во втором случае.

В Clang зарегистрирована ошибка под названием: Деструктор по умолчанию предотвращает использование constexpr для оператора копирования/перемещения по умолчанию, которая показывает те же симптомы, что и код в OP.

Комментарии из отчета об ошибке гласят:

When defaulted destructor is commented out (i.e. not user declared), then errors cease to exist.

а также

The problem also goes away if you declare the destructor before the copy assignment operator.

Это верно и для кода в вопросе.

Как указывает @YSC, здесь есть еще одна актуальная цитата: [dcl.fct.def.default]/3, в которой говорится:

An explicitly-defaulted function that is not defined as deleted may be declared constexpr or consteval only if it would have been implicitly declared as constexpr. If a function is explicitly defaulted on its first declaration, it is implicitly considered to be constexpr if the implicit declaration would be.

связанные: eel.is/c++draft/dcl.fct.def.default#3 и угорь.is/c++draft/class.copy.assign#10 (уже в кавычках)

YSC 15.03.2019 13:37

Могу ли я перевести это как: изменение порядка определения деструктора и копирования (как это сделал OP для struct A2 и struct A3) не должно иметь никакого эффекта (для компиляции кода без ошибок)?

Scheff's Cat 15.03.2019 13:43
Ответ принят как подходящий

Я думаю, что все три компилятора ошибаются.

[dcl.fct.def.default]/3 говорит:

An explicitly-defaulted function that is not defined as deleted may be declared constexpr or consteval only if it would have been implicitly declared as constexpr. If a function is explicitly defaulted on its first declaration, it is implicitly considered to be constexpr if the implicit declaration would be.

Когда неявно объявляется оператор присваивания копии constexpr? [класс.копия.назначить]/10:

The implicitly-defined copy/move assignment operator is constexpr if

  • X is a literal type, and
  • [...]

Где литеральный тип, из [основные.типы]/10:

A type is a literal type if it is:

  • [...]
  • a possibly cv-qualified class type that has all of the following properties:

    • it has a trivial destructor,
    • [...]

A1 не имеет тривиального деструктора, поэтому его неявный оператор присваивания копирования не является constexpr. Следовательно, этот оператор присваивания копии имеет неправильный формат (ошибка gcc и msvc для принятия).

С двумя другими все в порядке, и отклонить A2 — это лязг.


Обратите внимание на последнюю часть [dcl.fct.def.default], которую я процитировал. На самом деле вам не нужно добавлять constexpr, если вы явно по умолчанию. Было бы неявно constexpr там, где это возможно.

Добавление constexprдолжен даст вам ошибку, если это не constexpr, что может быть весьма полезно.

Yakk - Adam Nevraumont 15.03.2019 19:01

Другие вопросы по теме