c++language-lawyerc++20friendcomparison-operators

Redeclaration of explicitly defaulted comparison operator makes it undefined


In the following program, struct A has default friend equality comparison operator, which is redeclared again to get the pointer of the function (&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{} ) );

All major compilers (GCC, Clang, MSVC) are fine with the program. Online demo: https://gcc.godbolt.org/z/dhcd8esKn

However if the line with static_assert( A{} == A{} ); is removed, then the same compilers start rejecting the program with the errors:

error: 'constexpr bool operator==(const A&, const A&)' used before its definition

note: undefined function 'operator==' cannot be used in a constant

note: failure was caused by call of undefined function or one not declared 'constexpr'

Could you please explain why above program is valid only in presence of static_assert( A{} == A{} ); before operator== redeclaration?


Solution

  • This seems to be a bug in the implementations tested. EDG accepts the program.

    The defaulted operator== should not be considered undefined because attempting to call it in a constant expression should cause its definition to be generated. See [dcl.fct.def.default]/5

    [...] A non-user-provided defaulted function (i.e., implicitly declared or explicitly defaulted in the class) that is not defined as deleted is implicitly defined when it is odr-used ([basic.def.odr]) or needed for constant evaluation ([expr.const]).

    "Needed for constant evaluation" is defined in [expr.const]/21

    An expression or conversion is potentially constant evaluated if it is:

    • a manifestly constant-evaluated expression,
    • a potentially-evaluated expression,
    • an immediate subexpression of a braced-init-list,
    • an expression of the form & cast-expression that occurs within a templated entity, or
    • a potentially-evaluated subexpression ([intro.execution]) of one of the above.

    A function or variable is needed for constant evaluation if it is:

    • a constexpr function that is named by an expression that is potentially constant evaluated, or
    • a potentially-constant variable named by a potentially constant evaluated expression.

    The operand of the static assertion, (&operator==)( A{}, A{} ), is a manifestly constant-evaluated expression. The id-expression operator== within it is a potentially-evaluated subexpression, so it is potentially constant evaluated, and it names the operator== declared in this example, which is a constexpr function, so that function is needed for constant evaluation.

    Note that the friend declaration and the out-of-class redeclaration declare the same function. So, although name lookup for the id-expression operator== finds only the out-of-class redeclaration in this case, that id-expression denotes the function to which that redeclaration binds the name operator==. So this expression that names the function is sufficient to make the function "needed for constant evaluation" even though the declaration found by the lookup is not the defining declaration.