Skip to content

Commit b1c960f

Browse files
committed
[Clang] Implement P0848 (Conditionally Trivial Special Member Functions)
This patch implements P0848 in Clang. During the instantiation of a C++ class, in `Sema::ActOnFields`, we evaluate constraints for all the SMFs and compare the constraints to compute the eligibility. We defer the computation of the type's [copy-]trivial bits from addedMember to the eligibility computation, like we did for destructors in D126194. `canPassInRegisters` is modified as well to better respect the ineligibility of functions. Note: Because of the non-implementation of DR1734 and DR1496, I treat deleted member functions as 'eligible' for the purpose of [copy-]triviallity. This is unfortunate, but I couldn't think of a way to make this make sense otherwise. Reviewed By: #clang-language-wg, cor3ntin, aaron.ballman Differential Revision: https://reviews.llvm.org/D128619
1 parent 613336d commit b1c960f

10 files changed

+759
-18
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,10 @@ C++20 Feature Support
167167
(C++14 [dcl.constexpr]p6 (CWG DR647/CWG DR1358))
168168
- Correctly defer dependent immediate function invocations until template instantiation.
169169
This fixes `GH55601 <https://github.com/llvm/llvm-project/issues/55601>`_.
170+
- Implemented "Conditionally Trivial Special Member Functions" (`P0848 <https://wg21.link/p0848r3>`_).
171+
Note: The handling of deleted functions is not yet compliant, as Clang
172+
does not implement `DR1496 <https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1496>`_
173+
and `DR1734 <https://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1734>`_.
170174

171175

172176

clang/lib/AST/DeclCXX.cpp

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -894,9 +894,11 @@ void CXXRecordDecl::addedMember(Decl *D) {
894894
// This is an extension in C++03.
895895
data().PlainOldData = false;
896896
}
897-
// We delay updating destructor relevant properties until
898-
// addedSelectedDestructor.
899-
// FIXME: Defer this for the other special member functions as well.
897+
// When instantiating a class, we delay updating the destructor and
898+
// triviality properties of the class until selecting a destructor and
899+
// computing the eligibility of its special member functions. This is
900+
// because there might be function constraints that we need to evaluate
901+
// and compare later in the instantiation.
900902
if (!Method->isIneligibleOrNotSelected()) {
901903
addedEligibleSpecialMemberFunction(Method, SMKind);
902904
}
@@ -1437,10 +1439,12 @@ void CXXRecordDecl::finishedDefaultedOrDeletedMember(CXXMethodDecl *D) {
14371439

14381440
// Update which trivial / non-trivial special members we have.
14391441
// addedMember will have skipped this step for this member.
1440-
if (D->isTrivial())
1441-
data().HasTrivialSpecialMembers |= SMKind;
1442-
else
1443-
data().DeclaredNonTrivialSpecialMembers |= SMKind;
1442+
if (!D->isIneligibleOrNotSelected()) {
1443+
if (D->isTrivial())
1444+
data().HasTrivialSpecialMembers |= SMKind;
1445+
else
1446+
data().DeclaredNonTrivialSpecialMembers |= SMKind;
1447+
}
14441448
}
14451449

14461450
void CXXRecordDecl::setCaptures(ASTContext &Context,

clang/lib/Frontend/InitPreprocessor.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,11 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
673673

674674
// C++20 features.
675675
if (LangOpts.CPlusPlus20) {
676-
//Builder.defineMacro("__cpp_aggregate_paren_init", "201902L");
676+
// Builder.defineMacro("__cpp_aggregate_paren_init", "201902L");
677+
678+
// P0848 is implemented, but we're still waiting for other concepts
679+
// issues to be addressed before bumping __cpp_concepts up to 202002L.
680+
// Refer to the discussion of this at https://reviews.llvm.org/D128619.
677681
Builder.defineMacro("__cpp_concepts", "201907L");
678682
Builder.defineMacro("__cpp_conditional_explicit", "201806L");
679683
//Builder.defineMacro("__cpp_consteval", "201811L");

clang/lib/Sema/SemaDecl.cpp

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17968,7 +17968,6 @@ void Sema::ActOnLastBitfield(SourceLocation DeclLoc,
1796817968
AllIvarDecls.push_back(Ivar);
1796917969
}
1797017970

17971-
namespace {
1797217971
/// [class.dtor]p4:
1797317972
/// At the end of the definition of a class, overload resolution is
1797417973
/// performed among the prospective destructors declared in that class with
@@ -17977,7 +17976,7 @@ namespace {
1797717976
///
1797817977
/// We do the overload resolution here, then mark the selected constructor in the AST.
1797917978
/// Later CXXRecordDecl::getDestructor() will return the selected constructor.
17980-
void ComputeSelectedDestructor(Sema &S, CXXRecordDecl *Record) {
17979+
static void ComputeSelectedDestructor(Sema &S, CXXRecordDecl *Record) {
1798117980
if (!Record->hasUserDeclaredDestructor()) {
1798217981
return;
1798317982
}
@@ -18035,7 +18034,135 @@ void ComputeSelectedDestructor(Sema &S, CXXRecordDecl *Record) {
1803518034
Record->addedSelectedDestructor(dyn_cast<CXXDestructorDecl>(OCS.begin()->Function));
1803618035
}
1803718036
}
18038-
} // namespace
18037+
18038+
/// [class.mem.special]p5
18039+
/// Two special member functions are of the same kind if:
18040+
/// - they are both default constructors,
18041+
/// - they are both copy or move constructors with the same first parameter
18042+
/// type, or
18043+
/// - they are both copy or move assignment operators with the same first
18044+
/// parameter type and the same cv-qualifiers and ref-qualifier, if any.
18045+
static bool AreSpecialMemberFunctionsSameKind(ASTContext &Context,
18046+
CXXMethodDecl *M1,
18047+
CXXMethodDecl *M2,
18048+
Sema::CXXSpecialMember CSM) {
18049+
if (CSM == Sema::CXXDefaultConstructor)
18050+
return true;
18051+
if (!Context.hasSameType(M1->getParamDecl(0)->getType(),
18052+
M2->getParamDecl(0)->getType()))
18053+
return false;
18054+
if (!Context.hasSameType(M1->getThisType(), M2->getThisType()))
18055+
return false;
18056+
18057+
return true;
18058+
}
18059+
18060+
/// [class.mem.special]p6:
18061+
/// An eligible special member function is a special member function for which:
18062+
/// - the function is not deleted,
18063+
/// - the associated constraints, if any, are satisfied, and
18064+
/// - no special member function of the same kind whose associated constraints
18065+
/// [CWG2595], if any, are satisfied is more constrained.
18066+
static void SetEligibleMethods(Sema &S, CXXRecordDecl *Record,
18067+
ArrayRef<CXXMethodDecl *> Methods,
18068+
Sema::CXXSpecialMember CSM) {
18069+
SmallVector<bool, 4> SatisfactionStatus;
18070+
18071+
for (CXXMethodDecl *Method : Methods) {
18072+
const Expr *Constraints = Method->getTrailingRequiresClause();
18073+
if (!Constraints)
18074+
SatisfactionStatus.push_back(true);
18075+
else {
18076+
ConstraintSatisfaction Satisfaction;
18077+
if (S.CheckFunctionConstraints(Method, Satisfaction))
18078+
SatisfactionStatus.push_back(false);
18079+
else
18080+
SatisfactionStatus.push_back(Satisfaction.IsSatisfied);
18081+
}
18082+
}
18083+
18084+
for (size_t i = 0; i < Methods.size(); i++) {
18085+
if (!SatisfactionStatus[i])
18086+
continue;
18087+
CXXMethodDecl *Method = Methods[i];
18088+
const Expr *Constraints = Method->getTrailingRequiresClause();
18089+
bool AnotherMethodIsMoreConstrained = false;
18090+
for (size_t j = 0; j < Methods.size(); j++) {
18091+
if (i == j || !SatisfactionStatus[j])
18092+
continue;
18093+
CXXMethodDecl *OtherMethod = Methods[j];
18094+
if (!AreSpecialMemberFunctionsSameKind(S.Context, Method, OtherMethod,
18095+
CSM))
18096+
continue;
18097+
18098+
const Expr *OtherConstraints = OtherMethod->getTrailingRequiresClause();
18099+
if (!OtherConstraints)
18100+
continue;
18101+
if (!Constraints) {
18102+
AnotherMethodIsMoreConstrained = true;
18103+
break;
18104+
}
18105+
if (S.IsAtLeastAsConstrained(OtherMethod, {OtherConstraints}, Method,
18106+
{Constraints},
18107+
AnotherMethodIsMoreConstrained)) {
18108+
// There was an error with the constraints comparison. Exit the loop
18109+
// and don't consider this function eligible.
18110+
AnotherMethodIsMoreConstrained = true;
18111+
}
18112+
if (AnotherMethodIsMoreConstrained)
18113+
break;
18114+
}
18115+
// FIXME: Do not consider deleted methods as eligible after implementing
18116+
// DR1734 and DR1496.
18117+
if (!AnotherMethodIsMoreConstrained) {
18118+
Method->setIneligibleOrNotSelected(false);
18119+
Record->addedEligibleSpecialMemberFunction(Method, 1 << CSM);
18120+
}
18121+
}
18122+
}
18123+
18124+
static void ComputeSpecialMemberFunctionsEligiblity(Sema &S,
18125+
CXXRecordDecl *Record) {
18126+
SmallVector<CXXMethodDecl *, 4> DefaultConstructors;
18127+
SmallVector<CXXMethodDecl *, 4> CopyConstructors;
18128+
SmallVector<CXXMethodDecl *, 4> MoveConstructors;
18129+
SmallVector<CXXMethodDecl *, 4> CopyAssignmentOperators;
18130+
SmallVector<CXXMethodDecl *, 4> MoveAssignmentOperators;
18131+
18132+
for (auto *Decl : Record->decls()) {
18133+
auto *MD = dyn_cast<CXXMethodDecl>(Decl);
18134+
if (!MD) {
18135+
auto *FTD = dyn_cast<FunctionTemplateDecl>(Decl);
18136+
if (FTD)
18137+
MD = dyn_cast<CXXMethodDecl>(FTD->getTemplatedDecl());
18138+
}
18139+
if (!MD)
18140+
continue;
18141+
if (auto *CD = dyn_cast<CXXConstructorDecl>(MD)) {
18142+
if (CD->isInvalidDecl())
18143+
continue;
18144+
if (CD->isDefaultConstructor())
18145+
DefaultConstructors.push_back(MD);
18146+
else if (CD->isCopyConstructor())
18147+
CopyConstructors.push_back(MD);
18148+
else if (CD->isMoveConstructor())
18149+
MoveConstructors.push_back(MD);
18150+
} else if (MD->isCopyAssignmentOperator()) {
18151+
CopyAssignmentOperators.push_back(MD);
18152+
} else if (MD->isMoveAssignmentOperator()) {
18153+
MoveAssignmentOperators.push_back(MD);
18154+
}
18155+
}
18156+
18157+
SetEligibleMethods(S, Record, DefaultConstructors,
18158+
Sema::CXXDefaultConstructor);
18159+
SetEligibleMethods(S, Record, CopyConstructors, Sema::CXXCopyConstructor);
18160+
SetEligibleMethods(S, Record, MoveConstructors, Sema::CXXMoveConstructor);
18161+
SetEligibleMethods(S, Record, CopyAssignmentOperators,
18162+
Sema::CXXCopyAssignment);
18163+
SetEligibleMethods(S, Record, MoveAssignmentOperators,
18164+
Sema::CXXMoveAssignment);
18165+
}
1803918166

1804018167
void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
1804118168
ArrayRef<Decl *> Fields, SourceLocation LBrac,
@@ -18063,9 +18190,6 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
1806318190
RecordDecl *Record = dyn_cast<RecordDecl>(EnclosingDecl);
1806418191
CXXRecordDecl *CXXRecord = dyn_cast<CXXRecordDecl>(EnclosingDecl);
1806518192

18066-
if (CXXRecord && !CXXRecord->isDependentType())
18067-
ComputeSelectedDestructor(*this, CXXRecord);
18068-
1806918193
// Start counting up the number of named members; make sure to include
1807018194
// members of anonymous structs and unions in the total.
1807118195
unsigned NumNamedMembers = 0;
@@ -18351,6 +18475,8 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
1835118475
Completed = true;
1835218476
}
1835318477
}
18478+
ComputeSelectedDestructor(*this, CXXRecord);
18479+
ComputeSpecialMemberFunctionsEligiblity(*this, CXXRecord);
1835418480
}
1835518481
}
1835618482

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6618,7 +6618,7 @@ static bool canPassInRegisters(Sema &S, CXXRecordDecl *D,
66186618
bool CopyCtorIsTrivial = false, CopyCtorIsTrivialForCall = false;
66196619
bool DtorIsTrivialForCall = false;
66206620

6621-
// If a class has at least one non-deleted, trivial copy constructor, it
6621+
// If a class has at least one eligible, trivial copy constructor, it
66226622
// is passed according to the C ABI. Otherwise, it is passed indirectly.
66236623
//
66246624
// Note: This permits classes with non-trivial copy or move ctors to be
@@ -6633,7 +6633,8 @@ static bool canPassInRegisters(Sema &S, CXXRecordDecl *D,
66336633
}
66346634
} else {
66356635
for (const CXXConstructorDecl *CD : D->ctors()) {
6636-
if (CD->isCopyConstructor() && !CD->isDeleted()) {
6636+
if (CD->isCopyConstructor() && !CD->isDeleted() &&
6637+
!CD->isIneligibleOrNotSelected()) {
66376638
if (CD->isTrivial())
66386639
CopyCtorIsTrivial = true;
66396640
if (CD->isTrivialForCall())

clang/lib/Sema/SemaTemplateInstantiateDecl.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2481,6 +2481,9 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
24812481
Constructor->getConstexprKind(), InheritedConstructor(),
24822482
TrailingRequiresClause);
24832483
Method->setRangeEnd(Constructor->getEndLoc());
2484+
if (Constructor->isDefaultConstructor() ||
2485+
Constructor->isCopyOrMoveConstructor())
2486+
Method->setIneligibleOrNotSelected(true);
24842487
} else if (CXXDestructorDecl *Destructor = dyn_cast<CXXDestructorDecl>(D)) {
24852488
Method = CXXDestructorDecl::Create(
24862489
SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo,
@@ -2503,6 +2506,8 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
25032506
SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo, SC,
25042507
D->UsesFPIntrin(), D->isInlineSpecified(), D->getConstexprKind(),
25052508
D->getEndLoc(), TrailingRequiresClause);
2509+
if (D->isMoveAssignmentOperator() || D->isCopyAssignmentOperator())
2510+
Method->setIneligibleOrNotSelected(true);
25062511
}
25072512

25082513
if (D->isInlined())
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// RUN: %clang_cc1 -std=c++20 -triple x86_64-pc-linux %s -ast-dump | FileCheck %s
2+
// RUN: %clang_cc1 -std=c++20 -triple x86_64-pc-win32 %s -ast-dump | FileCheck %s
3+
4+
template<class X>
5+
struct DefaultConstructibleWithTemplate {
6+
template<class T = int>
7+
DefaultConstructibleWithTemplate();
8+
};
9+
10+
void f() {
11+
DefaultConstructibleWithTemplate<int> x;
12+
}
13+
14+
// CHECK: | `-ClassTemplateSpecializationDecl {{.*}} struct DefaultConstructibleWithTemplate definition
15+
// CHECK: | | |-CXXConstructorDecl {{.*}} DefaultConstructibleWithTemplate 'void ()'

0 commit comments

Comments
 (0)