В следующей программе struct A
имеет оператор сравнения равенства друзей по умолчанию, который переобъявляется снова, чтобы получить указатель на функцию (&operator==)
:
struct A {
friend constexpr bool operator ==(const A &, const A &) noexcept = default;
};
static_assert( A{} == A{} ); //if this line is removed the program fails
constexpr bool operator ==(const A &, const A &) noexcept;
static_assert( (&operator==)( A{}, A{} ) );
Все основные компиляторы (GCC, Clang, MSVC) нормально работают с программой. Онлайн-демо: https://gcc.godbolt.org/z/dhcd8esKn
Однако если убрать строку с static_assert( A{} == A{} );
, то те же компиляторы начнут отклонять программу с ошибками:
ошибка: 'constexpr booloperator==(const A&, const A&)' используется перед его определением
примечание: неопределенная функция 'operator==' не может использоваться в константе
примечание: сбой был вызван вызовом неопределенной функции или функции, не объявленной «constexpr».
Не могли бы вы объяснить, почему вышеуказанная программа действительна только при наличии static_assert( A{} == A{} );
до operator==
передекларирования?
Похоже, это ошибка в протестированных реализациях. EDG принимает программу.
Значение по умолчанию operator==
не следует считать неопределенным, поскольку попытка вызвать его в постоянном выражении должна привести к созданию его определения. См. [dcl.fct.def.default]/5
[...] Функция по умолчанию, не предоставленная пользователем (т. е. неявно объявленная или явно заданная по умолчанию в классе), которая не определена как удаленная, определяется неявно, когда она используется odr ([basic.def.odr]) или необходим для постоянной оценки ([expr.const]).
«Необходимо для постоянной оценки» определено в [expr.const]/21
Выражение или преобразование потенциально вычисляется как константа, если оно:
- выражение с явно константной оценкой,
- потенциально оцениваемое выражение,
- непосредственное подвыражение списка инициализации в скобках,
- выражение формы
&
cast-expression, которое встречается внутри шаблонной сущности, или- потенциально оцениваемое подвыражение ([intro.execution]) одного из вышеперечисленных.
Функция или переменная необходима для постоянного вычисления, если она:
- функция constexpr, названная выражением, которое потенциально может вычисляться как константа, или
- потенциально константная переменная, названная потенциально константным вычисленным выражением.
Операнд статического утверждения (&operator==)( A{}, A{} )
явно является выражением, вычисляемым константой. Выражение id operator==
внутри него является потенциально вычисляемым подвыражением, поэтому оно потенциально оценивается константой и называет operator==
, объявленную в этом примере, которая является функцией constexpr, поэтому эта функция необходима для постоянной оценки.
Обратите внимание, что объявление друга и повторное объявление вне класса объявляют одну и ту же функцию. Таким образом, хотя поиск имени для выражения идентификатора operator==
в этом случае находит только повторное объявление вне класса, это выражение идентификатора обозначает функцию, с которой это повторное объявление связывает имя operator==
. Таким образом, этого выражения, которое называет функцию, достаточно, чтобы сделать функцию «необходимой для постоянного вычисления», даже если объявление, найденное при поиске, не является определяющим объявлением.
Веселая проблема! Я подозреваю, что экземпляр друга
=default
не создается до тех пор, пока он не понадобится. Первый static_assert принудительно генерирует его, поэтому у него есть адрес, и, следовательно, второй static_assert может принять его адрес. Но я не языковой юрист, поэтому надеюсь, что кто-нибудь из них вмешается и прояснит эту загадку.