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