Index: clang/include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- clang/include/clang/Basic/DiagnosticSemaKinds.td +++ clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -9087,6 +9087,8 @@ def err_defaulted_comparison_param : Error< "invalid parameter type for defaulted %sub{select_defaulted_comparison_kind}0" "; found %1, expected %2%select{| or %4}3">; +def err_defaulted_comparison_function_not_friend : Error< + "defaulted %0 is not a friend of %1">; def err_defaulted_comparison_param_mismatch : Error< "parameters for defaulted %sub{select_defaulted_comparison_kind}0 " "must have the same type%diff{ (found $ vs $)|}1,2">; Index: clang/lib/Sema/SemaDeclCXX.cpp =================================================================== --- clang/lib/Sema/SemaDeclCXX.cpp +++ clang/lib/Sema/SemaDeclCXX.cpp @@ -8365,9 +8365,21 @@ bool Sema::CheckExplicitlyDefaultedComparison(Scope *S, FunctionDecl *FD, DefaultedComparisonKind DCK) { assert(DCK != DefaultedComparisonKind::None && "not a defaulted comparison"); + CXXRecordDecl *RD = + dyn_cast(FD->getCanonicalDecl()->getLexicalDeclContext()); - CXXRecordDecl *RD = dyn_cast(FD->getLexicalDeclContext()); - assert(RD && "defaulted comparison is not defaulted in a class"); + if (!RD) { + RD = dyn_cast(FD->getLexicalDeclContext()); + } + + // If the lexical decl context is null then the function is not a friend of a + // record + if (!RD) { + Diag(FD->getLocation(), diag::err_defaulted_comparison_function_not_friend) + << FD << FD->getParamDecl(0)->getType(); + + return true; + } // Perform any unqualified lookups we're going to need to default this // function. @@ -8441,9 +8453,6 @@ MD->setType(Context.getFunctionType(FPT->getReturnType(), FPT->getParamTypes(), EPI)); } - } else { - // A non-member function declared in a class must be a friend. - assert(FD->getFriendObjectKind() && "expected a friend declaration"); } // C++2a [class.eq]p1, [class.rel]p1: @@ -8601,7 +8610,9 @@ { // Build and set up the function body. - CXXRecordDecl *RD = cast(FD->getLexicalParent()); + + CXXRecordDecl *RD = + cast(FD->getCanonicalDecl()->getLexicalDeclContext()); SourceLocation BodyLoc = FD->getEndLoc().isValid() ? FD->getEndLoc() : FD->getLocation(); StmtResult Body = @@ -17056,12 +17067,6 @@ return; } - if (DefKind.isComparison() && - !isa(FD->getLexicalDeclContext())) { - Diag(FD->getLocation(), diag::err_defaulted_comparison_out_of_class) - << (int)DefKind.asComparison(); - return; - } // Issue compatibility warning. We already warned if the operator is // 'operator<=>' when parsing the '<=>' token. @@ -17085,12 +17090,18 @@ FD->setWillHaveBody(false); // If this definition appears within the record, do the checking when - // the record is complete. This is always the case for a defaulted - // comparison. - if (DefKind.isComparison()) + // the record is complete. If it's defined outside the record define + // it now. + if (DefKind.isComparison()) { + if (!isa(FD->getLexicalDeclContext()) && + !CheckExplicitlyDefaultedComparison(getCurScope(), FD, + DefKind.asComparison())) { + + DefineDefaultedFunction(*this, FD, DefaultLoc); + } return; + } auto *MD = cast(FD); - const FunctionDecl *Primary = FD; if (const FunctionDecl *Pattern = FD->getTemplateInstantiationPattern()) // Ask the template instantiation pattern that actually had the Index: clang/lib/Sema/SemaTemplateInstantiate.cpp =================================================================== --- clang/lib/Sema/SemaTemplateInstantiate.cpp +++ clang/lib/Sema/SemaTemplateInstantiate.cpp @@ -753,11 +753,17 @@ << MD->isExplicitlyDefaulted() << DFK.asSpecialMember() << Context.getTagDeclType(MD->getParent()); } else if (DFK.isComparison()) { - Diags.Report(Active->PointOfInstantiation, - diag::note_comparison_synthesized_at) - << (int)DFK.asComparison() - << Context.getTagDeclType( - cast(FD->getLexicalDeclContext())); + auto *RD = dyn_cast( + FD->getCanonicalDecl()->getLexicalDeclContext()); + if (RD) { + Diags.Report(Active->PointOfInstantiation, + diag::note_comparison_synthesized_at) + << (int)DFK.asComparison() << Context.getTagDeclType(RD); + } else { + Diags.Report(Active->PointOfInstantiation, + diag::note_comparison_synthesized_at) + << (int)DFK.asComparison() << FD; + } } break; } Index: clang/test/CXX/class/class.compare/class.compare.default/p1.cpp =================================================================== --- clang/test/CXX/class/class.compare/class.compare.default/p1.cpp +++ clang/test/CXX/class/class.compare/class.compare.default/p1.cpp @@ -1,8 +1,6 @@ // RUN: %clang_cc1 -std=c++2a -verify %s struct B {}; -bool operator==(const B&, const B&) = default; // expected-error {{equality comparison operator can only be defaulted in a class definition}} expected-note {{candidate}} -bool operator<=>(const B&, const B&) = default; // expected-error {{three-way comparison operator can only be defaulted in a class definition}} template bool operator<(const B&, const B&) = default; // expected-error {{comparison operator template cannot be defaulted}} @@ -29,11 +27,6 @@ bool operator==(const A&) const = default; // expected-error {{comparison operator template cannot be defaulted}} }; -// FIXME: The wording is not clear as to whether these are valid, but the -// intention is that they are not. -bool operator<(const A&, const A&) = default; // expected-error {{relational comparison operator can only be defaulted in a class definition}} -bool A::operator<(const A&) const = default; // expected-error {{can only be defaulted in a class definition}} - template struct Dependent { using U = typename T::type; bool operator==(U) const = default; // expected-error {{found 'Dependent::U'}} Index: clang/test/CXX/class/class.compare/class.compare.default/p6.cpp =================================================================== --- /dev/null +++ clang/test/CXX/class/class.compare/class.compare.default/p6.cpp @@ -0,0 +1,53 @@ +// RUN: %clang_cc1 -std=c++2a -verify %s + +// Test defaulted ref params +struct A { + bool operator==(const A &) const; + friend bool operator!=(const A &, const A &); +}; + +bool A::operator==(const A &) const = default; +bool operator!=(const A &, const A &) = default; + +// Test defaulted value params + +struct B { + friend bool operator==(B, B); + bool operator==(B) const; +}; +bool operator==(B, B) = default; + +bool B::operator==(B) const = default; // expected-error {{invalid parameter type for defaulted equality comparison operator; found 'B', expected 'const B &'}} + +// Test for friend of C but not friend of A +struct C { + friend bool operator==(const A &, const A &); +}; +bool operator==(const A &, const A &) = default; // expected-error {{invalid parameter type for defaulted equality comparison operator; found 'const A &', expected 'C' or 'const C &'}} + +struct D { + struct E { + bool operator==(const E &) const = default; + friend bool operator!=(const E &, const E &); + }; +}; + +bool operator!=(const D::E &, const D::E &) = default; + +template +struct F { + T __Ignore; + bool operator==(const F &) const = default; + friend bool operator!=(const F &, const F &); +}; + +const bool R0 = F{} == F{}; + +template +bool operator!=(const F &, const F &) = default; // expected-error {{comparison operator template cannot be defaulted}} + +struct G { +}; + +bool G::operator==(const G &) const = default; // expected-error {{out-of-line definition of 'operator==' does not match any declaration in 'G'}} +bool operator!=(const G &, const G &) = default; // expected-error {{defaulted 'operator!=' is not a friend of 'const G &'}} \ No newline at end of file Index: clang/www/cxx_status.html =================================================================== --- clang/www/cxx_status.html +++ clang/www/cxx_status.html @@ -996,7 +996,7 @@ P2085R0 - No + Clang 13 Access checking on specializations