diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -491,9 +491,10 @@ that can be used for such compatibility. The demangler now demangles symbols with named module attachment. -- As per "Conditionally Trivial Special Member Functions" (P0848), it is - now possible to overload destructors using concepts. Note that the rest - of the paper about other special member functions is not yet implemented. +- Implemented "Conditionally Trivial Special Member Functions" (`P0848 `_). + Note that the handling of deleted functions is not yet compliant, as Clang + does not implement `DR1496 `_ + and `DR1734 `_. C++2b Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -894,9 +894,8 @@ // This is an extension in C++03. data().PlainOldData = false; } - // We delay updating destructor relevant properties until - // addedSelectedDestructor. - // FIXME: Defer this for the other special member functions as well. + // We delay updating destructor related and triviality properties until + // selecting destructor and computing eligibility of SMFs. if (!Method->isIneligibleOrNotSelected()) { addedEligibleSpecialMemberFunction(Method, SMKind); } @@ -1437,10 +1436,13 @@ // Update which trivial / non-trivial special members we have. // addedMember will have skipped this step for this member. - if (D->isTrivial()) - data().HasTrivialSpecialMembers |= SMKind; - else - data().DeclaredNonTrivialSpecialMembers |= SMKind; + if (!D->isIneligibleOrNotSelected()) { + if (D->isTrivial()) { + data().HasTrivialSpecialMembers |= SMKind; + } + else + data().DeclaredNonTrivialSpecialMembers |= SMKind; + } } void CXXRecordDecl::setCaptures(ASTContext &Context, diff --git a/clang/lib/Frontend/InitPreprocessor.cpp b/clang/lib/Frontend/InitPreprocessor.cpp --- a/clang/lib/Frontend/InitPreprocessor.cpp +++ b/clang/lib/Frontend/InitPreprocessor.cpp @@ -674,7 +674,7 @@ // C++20 features. if (LangOpts.CPlusPlus20) { //Builder.defineMacro("__cpp_aggregate_paren_init", "201902L"); - Builder.defineMacro("__cpp_concepts", "201907L"); + Builder.defineMacro("__cpp_concepts", "202002L"); Builder.defineMacro("__cpp_conditional_explicit", "201806L"); //Builder.defineMacro("__cpp_consteval", "201811L"); Builder.defineMacro("__cpp_constexpr_dynamic_alloc", "201907L"); diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -17845,6 +17845,123 @@ Record->addedSelectedDestructor(dyn_cast(OCS.begin()->Function)); } } + +/// Assuming the suggested solution to CWG2595 gets accepted: +/// [class.mem.special]p6: +/// An eligible special member function is a special member function for which: +/// - the function is not deleted, +/// - the associated constraints, if any, are satisfied, and +/// - no special member function of the same kind whose associated constraints, +/// if any, are satisfied is more constrained. +void ComputeSpecialMemberMethodsEligiblity(Sema &S, CXXRecordDecl *Record) { + SmallVector DefaultConstructors; + SmallVector CopyConstructors; + SmallVector MoveConstructors; + SmallVector CopyAssignmentOperators; + SmallVector MoveAssignmentOperators; + + for (auto *Decl : Record->decls()) { + auto* MD = dyn_cast(Decl); + if (!MD) + continue; + if (auto *CD = dyn_cast(MD)) { + if (CD->isInvalidDecl()) + continue; + if (CD->isDefaultConstructor()) + DefaultConstructors.push_back(MD); + else if (CD->isCopyConstructor()) + CopyConstructors.push_back(MD); + else if (CD->isMoveConstructor()) + MoveConstructors.push_back(MD); + } else if (MD->isCopyAssignmentOperator()) { + CopyAssignmentOperators.push_back(MD); + } else if (MD->isMoveAssignmentOperator()) { + MoveAssignmentOperators.push_back(MD); + } + } + + auto AreSameKind = [](CXXMethodDecl *M1, CXXMethodDecl *M2, + Sema::CXXSpecialMember CSM) { + /// [class.mem.special]p5 + /// Two special member functions are of the same kind if: + /// - they are both default constructors, + /// - they are both copy or move constructors with the same first parameter + /// type, or + /// - they are both copy or move assignment operators with the same first + /// parameter type and the same cv-qualifiers and ref-qualifier, if any. + if (CSM == Sema::CXXDefaultConstructor) + return true; + if (M1->getParamDecl(0)->getType() != M2->getParamDecl(0)->getType()) + return false; + if (M1->getThisType() != M2->getThisType()) + return false; + + return true; + }; + + auto SetEligibleMethods = [&S, &Record, + AreSameKind](ArrayRef Methods, + Sema::CXXSpecialMember CSM) { + SmallVector SatisfactionStatus; + + for (auto *Method : Methods) { + const Expr *Constraints = Method->getTrailingRequiresClause(); + if (!Constraints) + SatisfactionStatus.push_back(true); + else { + ConstraintSatisfaction Satisfaction; + if (S.CheckFunctionConstraints(Method, Satisfaction)) + SatisfactionStatus.push_back(false); + else + SatisfactionStatus.push_back(Satisfaction.IsSatisfied); + } + } + + for (size_t i = 0; i < Methods.size(); i++) { + if (!SatisfactionStatus[i]) + continue; + auto *MethodI = Methods[i]; + const Expr *ConstraintsI = MethodI->getTrailingRequiresClause(); + bool AnotherMethodIsMoreConstrained = false; + for (size_t j = 0; j < Methods.size(); j++) { + if (i == j || !SatisfactionStatus[j]) + continue; + auto *MethodJ = Methods[j]; + if (!AreSameKind(MethodI, MethodJ, CSM)) + continue; + + const Expr *ConstraintsJ = MethodJ->getTrailingRequiresClause(); + if (!ConstraintsJ) + continue; + if (!ConstraintsI) { + AnotherMethodIsMoreConstrained = true; + break; + } + if (S.IsAtLeastAsConstrained(MethodJ, {ConstraintsJ}, MethodI, + {ConstraintsI}, + AnotherMethodIsMoreConstrained)) { + AnotherMethodIsMoreConstrained = true; + break; + } + if (AnotherMethodIsMoreConstrained) + break; + } + // FIXME: Do not consider deleted methods as eligible after implementing + // DR1734 and DR1496. + if (!AnotherMethodIsMoreConstrained) { + MethodI->setIneligibleOrNotSelected(false); + Record->addedEligibleSpecialMemberFunction(MethodI, 1 << CSM); + } + } + }; + + SetEligibleMethods(DefaultConstructors, Sema::CXXDefaultConstructor); + SetEligibleMethods(CopyConstructors, Sema::CXXCopyConstructor); + SetEligibleMethods(MoveConstructors, Sema::CXXMoveConstructor); + SetEligibleMethods(CopyAssignmentOperators, Sema::CXXCopyAssignment); + SetEligibleMethods(MoveAssignmentOperators, Sema::CXXMoveAssignment); +} + } // namespace void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl, @@ -17873,9 +17990,6 @@ RecordDecl *Record = dyn_cast(EnclosingDecl); CXXRecordDecl *CXXRecord = dyn_cast(EnclosingDecl); - if (CXXRecord && !CXXRecord->isDependentType()) - ComputeSelectedDestructor(*this, CXXRecord); - // Start counting up the number of named members; make sure to include // members of anonymous structs and unions in the total. unsigned NumNamedMembers = 0; @@ -18107,6 +18221,8 @@ // Okay, we successfully defined 'Record'. if (Record) { bool Completed = false; + + // FIXME: This block should probably move into SemaDeclCXX. if (CXXRecord) { if (!CXXRecord->isInvalidDecl()) { // Set access bits correctly on the directly-declared conversions. @@ -18160,6 +18276,8 @@ CXXRecord->completeDefinition(&FinalOverriders); Completed = true; } + ComputeSelectedDestructor(*this, CXXRecord); + ComputeSpecialMemberMethodsEligiblity(*this, CXXRecord); } } } diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -6612,7 +6612,7 @@ bool CopyCtorIsTrivial = false, CopyCtorIsTrivialForCall = false; bool DtorIsTrivialForCall = false; - // If a class has at least one non-deleted, trivial copy constructor, it + // If a class has at least one eligible, trivial copy constructor, it // is passed according to the C ABI. Otherwise, it is passed indirectly. // // Note: This permits classes with non-trivial copy or move ctors to be @@ -6627,7 +6627,8 @@ } } else { for (const CXXConstructorDecl *CD : D->ctors()) { - if (CD->isCopyConstructor() && !CD->isDeleted()) { + if (CD->isCopyConstructor() && !CD->isDeleted() && + !CD->isIneligibleOrNotSelected()) { if (CD->isTrivial()) CopyCtorIsTrivial = true; if (CD->isTrivialForCall()) diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -2473,6 +2473,8 @@ Constructor->getConstexprKind(), InheritedConstructor(), TrailingRequiresClause); Method->setRangeEnd(Constructor->getEndLoc()); + if (Constructor->isDefaultConstructor() || Constructor->isCopyOrMoveConstructor()) + Method->setIneligibleOrNotSelected(true); } else if (CXXDestructorDecl *Destructor = dyn_cast(D)) { Method = CXXDestructorDecl::Create( SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo, @@ -2495,6 +2497,8 @@ SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo, SC, D->UsesFPIntrin(), D->isInlineSpecified(), D->getConstexprKind(), D->getEndLoc(), TrailingRequiresClause); + if (D->isMoveAssignmentOperator() || D->isCopyAssignmentOperator()) + Method->setIneligibleOrNotSelected(true); } if (D->isInlined()) diff --git a/clang/test/Lexer/cxx-features.cpp b/clang/test/Lexer/cxx-features.cpp --- a/clang/test/Lexer/cxx-features.cpp +++ b/clang/test/Lexer/cxx-features.cpp @@ -52,7 +52,7 @@ #error "wrong value for __cpp_char8_t" #endif -#if check(concepts, 0, 0, 0, 0, 201907, 201907) +#if check(concepts, 0, 0, 0, 0, 202002L, 202002L) #error "wrong value for __cpp_concepts" #endif diff --git a/clang/test/SemaCXX/constrained-special-member-functions.cpp b/clang/test/SemaCXX/constrained-special-member-functions.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/constrained-special-member-functions.cpp @@ -0,0 +1,130 @@ +// RUN: %clang_cc1 -verify -std=c++20 %s +// expected-no-diagnostics + +template +concept C0 = (N == 0); +template +concept C1 = (N == 1); +template +concept C2 = (N == 2); + +// Checks are indexed by: +// Definition: +// 1. Explicitly defaulted definition +// 2. Deleted definition +// 3. User provided definition +// We have a less constrained user provided method that should not disable +// the (copyable) triviality of the type. + +// Note that because Clang does not implement DRs 1496 and 1734, we say some +// classes are trivial when the SMFs are deleted. + +template +struct DefaultConstructorChecker { + DefaultConstructorChecker() requires C0 = default; + DefaultConstructorChecker() requires C1 = delete; + DefaultConstructorChecker() requires C2; + DefaultConstructorChecker(); +}; +static_assert(__is_trivially_copyable(DefaultConstructorChecker<0>)); +static_assert(__is_trivially_copyable(DefaultConstructorChecker<1>)); +static_assert(__is_trivially_copyable(DefaultConstructorChecker<2>)); +static_assert(__is_trivially_copyable(DefaultConstructorChecker<3>)); +static_assert(__is_trivial(DefaultConstructorChecker<0>)); +// FIXME: DR1496 +static_assert(__is_trivial(DefaultConstructorChecker<1>)); +static_assert(!__is_trivial(DefaultConstructorChecker<2>)); +static_assert(!__is_trivial(DefaultConstructorChecker<3>)); + +template +struct CopyConstructorChecker { + CopyConstructorChecker(const CopyConstructorChecker&) requires C0 = default; + CopyConstructorChecker(const CopyConstructorChecker&) requires C1 = delete; + CopyConstructorChecker(const CopyConstructorChecker&) requires C2; + CopyConstructorChecker(const CopyConstructorChecker&); +}; + +static_assert(__is_trivially_copyable(CopyConstructorChecker<0>)); +// FIXME: DR1734 +static_assert(__is_trivially_copyable(CopyConstructorChecker<1>)); +static_assert(!__is_trivially_copyable(CopyConstructorChecker<2>)); +static_assert(!__is_trivially_copyable(CopyConstructorChecker<3>)); +static_assert(!__is_trivial(CopyConstructorChecker<0>)); +static_assert(!__is_trivial(CopyConstructorChecker<1>)); +static_assert(!__is_trivial(CopyConstructorChecker<2>)); +static_assert(!__is_trivial(CopyConstructorChecker<3>)); + +template +struct MoveConstructorChecker { + MoveConstructorChecker(MoveConstructorChecker&&) requires C0 = default; + MoveConstructorChecker(MoveConstructorChecker&&) requires C1 = delete; + MoveConstructorChecker(MoveConstructorChecker&&) requires C2; + MoveConstructorChecker(MoveConstructorChecker&&); +}; + +static_assert(__is_trivially_copyable(MoveConstructorChecker<0>)); +// FIXME: DR1734 +static_assert(__is_trivially_copyable(MoveConstructorChecker<1>)); +static_assert(!__is_trivially_copyable(MoveConstructorChecker<2>)); +static_assert(!__is_trivially_copyable(MoveConstructorChecker<3>)); +static_assert(!__is_trivial(MoveConstructorChecker<0>)); +static_assert(!__is_trivial(MoveConstructorChecker<1>)); +static_assert(!__is_trivial(MoveConstructorChecker<2>)); +static_assert(!__is_trivial(MoveConstructorChecker<3>)); + +template +struct MoveAssignmentChecker { + MoveAssignmentChecker& operator=(MoveAssignmentChecker&&) requires C0 = default; + MoveAssignmentChecker& operator=(MoveAssignmentChecker&&) requires C1 = delete; + MoveAssignmentChecker& operator=(MoveAssignmentChecker&&) requires C2; + MoveAssignmentChecker& operator=(MoveAssignmentChecker&&); +}; + +static_assert(__is_trivially_copyable(MoveAssignmentChecker<0>)); +// FIXME: DR1734. +static_assert(__is_trivially_copyable(MoveAssignmentChecker<1>)); +static_assert(!__is_trivially_copyable(MoveAssignmentChecker<2>)); +static_assert(!__is_trivially_copyable(MoveAssignmentChecker<3>)); +static_assert(__is_trivial(MoveAssignmentChecker<0>)); +// FIXME: DR1734. +static_assert(__is_trivial(MoveAssignmentChecker<1>)); +static_assert(!__is_trivial(MoveAssignmentChecker<2>)); +static_assert(!__is_trivial(MoveAssignmentChecker<3>)); + +template +struct CopyAssignmentChecker { + CopyAssignmentChecker& operator=(const CopyAssignmentChecker&) requires C0 = default; + CopyAssignmentChecker& operator=(const CopyAssignmentChecker&) requires C1 = delete; + CopyAssignmentChecker& operator=(const CopyAssignmentChecker&) requires C2; + CopyAssignmentChecker& operator=(const CopyAssignmentChecker&); +}; + +static_assert(__is_trivially_copyable(CopyAssignmentChecker<0>)); +// FIXME: DR1734. +static_assert(__is_trivially_copyable(CopyAssignmentChecker<1>)); +static_assert(!__is_trivially_copyable(CopyAssignmentChecker<2>)); +static_assert(!__is_trivially_copyable(CopyAssignmentChecker<3>)); +static_assert(__is_trivial(CopyAssignmentChecker<0>)); +// FIXME: DR1734. +static_assert(__is_trivial(CopyAssignmentChecker<1>)); +static_assert(!__is_trivial(CopyAssignmentChecker<2>)); +static_assert(!__is_trivial(CopyAssignmentChecker<3>)); + + +template +struct KindComparisonChecker1 { + KindComparisonChecker1& operator=(const KindComparisonChecker1&) requires C0 = default; + KindComparisonChecker1& operator=(KindComparisonChecker1&); +}; + +template +struct KindComparisonChecker2 { + KindComparisonChecker2& operator=(const KindComparisonChecker2&) requires C0 = default; + const KindComparisonChecker2& operator=(KindComparisonChecker2&) const; +}; + +static_assert(!__is_trivial(KindComparisonChecker1<0>)); +static_assert(!__is_trivially_copyable(KindComparisonChecker1<0>)); + +static_assert(!__is_trivial(KindComparisonChecker2<0>)); +static_assert(!__is_trivially_copyable(KindComparisonChecker2<0>)); diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -927,7 +927,7 @@ P0848R3 - No + Clang 15 P1616R1