Search code examples
c++language-lawyerc++20friendcomparison-operators

Can friend comparison operator be defined for a local class?


Since C++20 the compiler can generate default comparison operators for a class, including as friend non-member function, see (2) in cppreference.com.

I came across the code working in MSVC that does it for a local class inside a function:

void foo() {
    struct A;
    bool operator ==(const A&, const A&);
    struct A { 
        friend bool operator ==(const A&, const A&) = default;
    };
}

Unfortunately, it does not work in Clang or GCC, which complains:

error: cannot define friend function 'operator==' in a local class definition

Online demo: https://godbolt.org/z/Ts1fer1d1

There is a way to make the code accepted by GCC:

void foo() {
    struct A;
    bool operator ==(const A&, const A&);
    struct A { 
        friend bool operator ==(const A&, const A&);
    };
    bool operator ==(const A&, const A&) = default;
}

which now only prints some vague warning:

warning: declaration of 'bool operator==(const foo()::A&, const foo()::A&)' has 'extern' and is initialized

but the other two compilers does not like it, online demo: https://godbolt.org/z/he1zjj46G

As long as the compilers diverge, which one is correct in both examples above?


Solution

  • The standard is very clear on this:

    A function may be defined in a friend declaration of a class if and only if the class is a non-local class and the function name is unqualified.

    - [class.friend] p6

    Your first code sample is defining a friend in a local class, so it violates this paragraph.

    The second example defines a function at block scope, which is also clearly ill-formed:

    [...] A function shall be defined only in namespace or class scope. [...]

    - [dcl.fct.def.general] p2

    The fact that GCC compiles it is a compiler bug, and possibly related to the fact that GCC supports local functions as a compiler extension. The warning has 'extern' and is initialized it gives is nonsensical, and would normally occur in scenarios such as:

    // <source>:1:12: warning: 'x' initialized and declared 'extern'
    //     1 | extern int x = 0;
    //       |            ^
    extern int x = 0;
    

    Note: I've filed a bug report for this problem: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=111079