Объявление оператора присваивания по умолчанию как 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.

(живое демо)

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

Ответ 1

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

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

Явно дефолтная функция, которая не определена как удаленная, может быть объявлена constexpr или consteval только если она неявно была объявлена как constexpr. Если функция явно установлена по умолчанию в своем первом объявлении, она неявно считается constexpr если неявное объявление будет.

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

Неявно определенный оператор присваивания копирования/перемещения называется constexpr, если

  • X - буквальный тип, и
  • [...]

Где буквенный тип, из [basic.types]/10:

Тип является литеральным типом, если это:

  • [...]
  • возможно, cv-квалифицированный тип класса, который имеет все следующие свойства:

    • у него есть тривиальный деструктор,
    • [...]

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

Два других в порядке, и это лягушатник - отклонение A2.


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

Ответ 2

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

15.8.2 Оператор назначения копирования/перемещения [class.copy.assign]
...

10 Оператор назначения копирования/перемещения для класса X, который по умолчанию и не определен как удаленный, неявно определяется, когда он используется в odr (6.2) (например, когда он выбирается с помощью разрешения перегрузки для назначения объекту своего типа класса ) или когда он явно установлен по умолчанию после его первого объявления. Неявно определенный оператор присваивания копирования/перемещения называется constexpr если
(10.1) - X является литеральным типом, и
(10.2) - оператор присваивания, выбранный для копирования/перемещения каждого подобъекта прямого базового класса, является функцией constexpr, и
(10.3) - для каждого нестатического члена данных X который имеет тип класса (или его массив), оператор присваивания, выбранный для копирования/перемещения этого члена, является функцией constexpr.

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

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

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

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

Когда деструктор по умолчанию закомментирован (т.е. не объявлен пользователем), ошибки перестают существовать.

а также

Проблема также исчезнет, если вы объявите деструктор перед оператором назначения копирования.

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

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

Явно дефолтная функция, которая не определена как удаленная, может быть объявлена constexpr или consteval только если она неявно была объявлена как constexpr. Если функция явно установлена по умолчанию в своем первом объявлении, она неявно считается constexpr если неявное объявление будет.