[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
This commit is contained in:
Roy Jacobson 2022-08-26 00:51:06 +03:00
parent 613336da8c
commit b1c960fc6d
10 changed files with 759 additions and 18 deletions

View File

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

View File

@ -894,9 +894,11 @@ void CXXRecordDecl::addedMember(Decl *D) {
// 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.
// When instantiating a class, we delay updating the destructor and
// triviality properties of the class until selecting a destructor and
// computing the eligibility of its special member functions. This is
// because there might be function constraints that we need to evaluate
// and compare later in the instantiation.
if (!Method->isIneligibleOrNotSelected()) {
addedEligibleSpecialMemberFunction(Method, SMKind);
}
@ -1437,10 +1439,12 @@ void CXXRecordDecl::finishedDefaultedOrDeletedMember(CXXMethodDecl *D) {
// 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,

View File

@ -673,7 +673,11 @@ static void InitializeCPlusPlusFeatureTestMacros(const LangOptions &LangOpts,
// C++20 features.
if (LangOpts.CPlusPlus20) {
//Builder.defineMacro("__cpp_aggregate_paren_init", "201902L");
// Builder.defineMacro("__cpp_aggregate_paren_init", "201902L");
// P0848 is implemented, but we're still waiting for other concepts
// issues to be addressed before bumping __cpp_concepts up to 202002L.
// Refer to the discussion of this at https://reviews.llvm.org/D128619.
Builder.defineMacro("__cpp_concepts", "201907L");
Builder.defineMacro("__cpp_conditional_explicit", "201806L");
//Builder.defineMacro("__cpp_consteval", "201811L");

View File

@ -17968,7 +17968,6 @@ void Sema::ActOnLastBitfield(SourceLocation DeclLoc,
AllIvarDecls.push_back(Ivar);
}
namespace {
/// [class.dtor]p4:
/// At the end of the definition of a class, overload resolution is
/// performed among the prospective destructors declared in that class with
@ -17977,7 +17976,7 @@ namespace {
///
/// We do the overload resolution here, then mark the selected constructor in the AST.
/// Later CXXRecordDecl::getDestructor() will return the selected constructor.
void ComputeSelectedDestructor(Sema &S, CXXRecordDecl *Record) {
static void ComputeSelectedDestructor(Sema &S, CXXRecordDecl *Record) {
if (!Record->hasUserDeclaredDestructor()) {
return;
}
@ -18035,7 +18034,135 @@ void ComputeSelectedDestructor(Sema &S, CXXRecordDecl *Record) {
Record->addedSelectedDestructor(dyn_cast<CXXDestructorDecl>(OCS.begin()->Function));
}
}
} // namespace
/// [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.
static bool AreSpecialMemberFunctionsSameKind(ASTContext &Context,
CXXMethodDecl *M1,
CXXMethodDecl *M2,
Sema::CXXSpecialMember CSM) {
if (CSM == Sema::CXXDefaultConstructor)
return true;
if (!Context.hasSameType(M1->getParamDecl(0)->getType(),
M2->getParamDecl(0)->getType()))
return false;
if (!Context.hasSameType(M1->getThisType(), M2->getThisType()))
return false;
return true;
}
/// [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
/// [CWG2595], if any, are satisfied is more constrained.
static void SetEligibleMethods(Sema &S, CXXRecordDecl *Record,
ArrayRef<CXXMethodDecl *> Methods,
Sema::CXXSpecialMember CSM) {
SmallVector<bool, 4> SatisfactionStatus;
for (CXXMethodDecl *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;
CXXMethodDecl *Method = Methods[i];
const Expr *Constraints = Method->getTrailingRequiresClause();
bool AnotherMethodIsMoreConstrained = false;
for (size_t j = 0; j < Methods.size(); j++) {
if (i == j || !SatisfactionStatus[j])
continue;
CXXMethodDecl *OtherMethod = Methods[j];
if (!AreSpecialMemberFunctionsSameKind(S.Context, Method, OtherMethod,
CSM))
continue;
const Expr *OtherConstraints = OtherMethod->getTrailingRequiresClause();
if (!OtherConstraints)
continue;
if (!Constraints) {
AnotherMethodIsMoreConstrained = true;
break;
}
if (S.IsAtLeastAsConstrained(OtherMethod, {OtherConstraints}, Method,
{Constraints},
AnotherMethodIsMoreConstrained)) {
// There was an error with the constraints comparison. Exit the loop
// and don't consider this function eligible.
AnotherMethodIsMoreConstrained = true;
}
if (AnotherMethodIsMoreConstrained)
break;
}
// FIXME: Do not consider deleted methods as eligible after implementing
// DR1734 and DR1496.
if (!AnotherMethodIsMoreConstrained) {
Method->setIneligibleOrNotSelected(false);
Record->addedEligibleSpecialMemberFunction(Method, 1 << CSM);
}
}
}
static void ComputeSpecialMemberFunctionsEligiblity(Sema &S,
CXXRecordDecl *Record) {
SmallVector<CXXMethodDecl *, 4> DefaultConstructors;
SmallVector<CXXMethodDecl *, 4> CopyConstructors;
SmallVector<CXXMethodDecl *, 4> MoveConstructors;
SmallVector<CXXMethodDecl *, 4> CopyAssignmentOperators;
SmallVector<CXXMethodDecl *, 4> MoveAssignmentOperators;
for (auto *Decl : Record->decls()) {
auto *MD = dyn_cast<CXXMethodDecl>(Decl);
if (!MD) {
auto *FTD = dyn_cast<FunctionTemplateDecl>(Decl);
if (FTD)
MD = dyn_cast<CXXMethodDecl>(FTD->getTemplatedDecl());
}
if (!MD)
continue;
if (auto *CD = dyn_cast<CXXConstructorDecl>(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);
}
}
SetEligibleMethods(S, Record, DefaultConstructors,
Sema::CXXDefaultConstructor);
SetEligibleMethods(S, Record, CopyConstructors, Sema::CXXCopyConstructor);
SetEligibleMethods(S, Record, MoveConstructors, Sema::CXXMoveConstructor);
SetEligibleMethods(S, Record, CopyAssignmentOperators,
Sema::CXXCopyAssignment);
SetEligibleMethods(S, Record, MoveAssignmentOperators,
Sema::CXXMoveAssignment);
}
void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
ArrayRef<Decl *> Fields, SourceLocation LBrac,
@ -18063,9 +18190,6 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
RecordDecl *Record = dyn_cast<RecordDecl>(EnclosingDecl);
CXXRecordDecl *CXXRecord = dyn_cast<CXXRecordDecl>(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;
@ -18351,6 +18475,8 @@ void Sema::ActOnFields(Scope *S, SourceLocation RecLoc, Decl *EnclosingDecl,
Completed = true;
}
}
ComputeSelectedDestructor(*this, CXXRecord);
ComputeSpecialMemberFunctionsEligiblity(*this, CXXRecord);
}
}

View File

@ -6618,7 +6618,7 @@ static bool canPassInRegisters(Sema &S, CXXRecordDecl *D,
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
@ -6633,7 +6633,8 @@ static bool canPassInRegisters(Sema &S, CXXRecordDecl *D,
}
} 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())

View File

@ -2481,6 +2481,9 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
Constructor->getConstexprKind(), InheritedConstructor(),
TrailingRequiresClause);
Method->setRangeEnd(Constructor->getEndLoc());
if (Constructor->isDefaultConstructor() ||
Constructor->isCopyOrMoveConstructor())
Method->setIneligibleOrNotSelected(true);
} else if (CXXDestructorDecl *Destructor = dyn_cast<CXXDestructorDecl>(D)) {
Method = CXXDestructorDecl::Create(
SemaRef.Context, Record, StartLoc, NameInfo, T, TInfo,
@ -2503,6 +2506,8 @@ Decl *TemplateDeclInstantiator::VisitCXXMethodDecl(
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())

View File

@ -0,0 +1,15 @@
// RUN: %clang_cc1 -std=c++20 -triple x86_64-pc-linux %s -ast-dump | FileCheck %s
// RUN: %clang_cc1 -std=c++20 -triple x86_64-pc-win32 %s -ast-dump | FileCheck %s
template<class X>
struct DefaultConstructibleWithTemplate {
template<class T = int>
DefaultConstructibleWithTemplate();
};
void f() {
DefaultConstructibleWithTemplate<int> x;
}
// CHECK: | `-ClassTemplateSpecializationDecl {{.*}} struct DefaultConstructibleWithTemplate definition
// CHECK: | | |-CXXConstructorDecl {{.*}} DefaultConstructibleWithTemplate 'void ()'

View File

@ -0,0 +1,343 @@
// RUN: %clang_cc1 -std=c++20 -triple x86_64-pc-linux -ast-dump=json %s | FileCheck %s --check-prefixes=CHECK,LIN
// RUN: %clang_cc1 -std=c++20 -triple x86_64-pc-win32 -ast-dump=json %s | FileCheck %s
// This test validates that we compute correct AST properties of classes with
// conditionally trivial special member functions.
template <int N>
struct DefaultConstructorCheck {
DefaultConstructorCheck() requires(N == 1) = default;
DefaultConstructorCheck() requires(N == 2) = delete;
DefaultConstructorCheck() requires(N == 3);
DefaultConstructorCheck();
};
template struct DefaultConstructorCheck<1>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "canPassInRegisters": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "defaultCtor": {
// CHECK-NEXT: "defaultedIsConstexpr": true,
// CHECK-NEXT: "exists": true,
// CHECK-NEXT: "isConstexpr": true,
// CHECK-NEXT: "trivial": true,
// CHECK-NEXT: "userProvided": true
// CHECK-NEXT: },
// CHECK: "hasConstexprNonCopyMoveConstructor": true,
// CHECK-NEXT: "hasUserDeclaredConstructor": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isLiteral": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "isTrivial": true,
// CHECK-NEXT: "isTriviallyCopyable": true,
template struct DefaultConstructorCheck<2>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "canPassInRegisters": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "defaultCtor": {
// CHECK-NEXT: "defaultedIsConstexpr": true,
// CHECK-NEXT: "exists": true,
// CHECK-NEXT: "isConstexpr": true,
// CHECK-NEXT: "trivial": true,
// CHECK-NEXT: "userProvided": true
// CHECK-NEXT: },
// CHECK: "hasConstexprNonCopyMoveConstructor": true,
// CHECK-NEXT: "hasUserDeclaredConstructor": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isLiteral": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "isTrivial": true,
// CHECK-NEXT: "isTriviallyCopyable": true,
template struct DefaultConstructorCheck<3>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "canPassInRegisters": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "defaultCtor": {
// CHECK-NEXT: "defaultedIsConstexpr": true,
// CHECK-NEXT: "exists": true,
// CHECK-NEXT: "isConstexpr": true,
// CHECK-NEXT: "nonTrivial": true,
// CHECK-NEXT: "userProvided": true
// CHECK-NEXT: },
// CHECK: "hasConstexprNonCopyMoveConstructor": true,
// CHECK-NEXT: "hasUserDeclaredConstructor": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isLiteral": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "isTriviallyCopyable": true,
template <int N>
struct CopyConstructorCheck {
CopyConstructorCheck(const CopyConstructorCheck&) requires(N == 1) = default;
CopyConstructorCheck(const CopyConstructorCheck&) requires(N == 2) = delete;
CopyConstructorCheck(const CopyConstructorCheck&) requires(N == 3);
CopyConstructorCheck(const CopyConstructorCheck&);
};
template struct CopyConstructorCheck<1>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "canPassInRegisters": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "copyCtor": {
// CHECK-NEXT: "hasConstParam": true,
// CHECK-NEXT: "implicitHasConstParam": true,
// CHECK-NEXT: "trivial": true,
// CHECK-NEXT: "userDeclared": true
// CHECK-NEXT: },
// CHECK: "hasUserDeclaredConstructor": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "isTriviallyCopyable": true,
// CHECK-NEXT: "moveAssign": {},
template struct CopyConstructorCheck<2>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "copyCtor": {
// CHECK-NEXT: "hasConstParam": true,
// CHECK-NEXT: "implicitHasConstParam": true,
// CHECK-NEXT: "trivial": true,
// CHECK-NEXT: "userDeclared": true
// CHECK-NEXT: },
// CHECK: "hasUserDeclaredConstructor": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "isTriviallyCopyable": true,
// CHECK-NEXT: "moveAssign": {},
template struct CopyConstructorCheck<3>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "copyCtor": {
// CHECK-NEXT: "hasConstParam": true,
// CHECK-NEXT: "implicitHasConstParam": true,
// CHECK-NEXT: "nonTrivial": true,
// CHECK-NEXT: "userDeclared": true
// CHECK-NEXT: },
// CHECK: "hasUserDeclaredConstructor": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "moveAssign": {},
template <int N>
struct MoveConstructorCheck {
MoveConstructorCheck(MoveConstructorCheck&&) requires(N == 1) = default;
MoveConstructorCheck(MoveConstructorCheck&&) requires(N == 2) = delete;
MoveConstructorCheck(MoveConstructorCheck&&) requires(N == 3);
MoveConstructorCheck(MoveConstructorCheck&&);
};
template struct MoveConstructorCheck<1>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// LIN-NEXT: "canPassInRegisters": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "hasUserDeclaredConstructor": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "isTriviallyCopyable": true,
// CHECK-NEXT: "moveAssign": {},
// CHECK-NEXT: "moveCtor": {
// CHECK-NEXT: "exists": true,
// CHECK-NEXT: "trivial": true,
// CHECK-NEXT: "userDeclared": true
// CHECK-NEXT: }
template struct MoveConstructorCheck<2>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "hasUserDeclaredConstructor": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "isTriviallyCopyable": true,
// CHECK-NEXT: "moveAssign": {},
// CHECK-NEXT: "moveCtor": {
// CHECK-NEXT: "exists": true,
// CHECK-NEXT: "trivial": true,
// CHECK-NEXT: "userDeclared": true
// CHECK-NEXT: }
template struct MoveConstructorCheck<3>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "hasUserDeclaredConstructor": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "moveAssign": {},
// CHECK-NEXT: "moveCtor": {
// CHECK-NEXT: "exists": true,
// CHECK-NEXT: "nonTrivial": true,
// CHECK-NEXT: "userDeclared": true
// CHECK-NEXT: }
template <int N>
struct CopyAssignmentCheck {
CopyAssignmentCheck& operator=(const CopyAssignmentCheck&) requires(N == 1) = default;
CopyAssignmentCheck& operator=(const CopyAssignmentCheck&) requires(N == 2) = delete;
CopyAssignmentCheck& operator=(const CopyAssignmentCheck&) requires(N == 3);
CopyAssignmentCheck& operator=(const CopyAssignmentCheck&);
};
template struct CopyAssignmentCheck<1>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "canPassInRegisters": true,
// CHECK-NEXT "copyAssign": {
// CHECK-NEXT "hasConstParam": true,
// CHECK-NEXT "implicitHasConstParam": true,
// CHECK-NEXT "trivial": true,
// CHECK-NEXT "userDeclared": true
// CHECK-NEXT },
// CHECK: "hasConstexprNonCopyMoveConstructor": true,
// CHECK-NEXT "isAggregate": true,
// CHECK-NEXT "isEmpty": true,
// CHECK-NEXT "isLiteral": true,
// CHECK-NEXT "isStandardLayout": true,
// CHECK-NEXT "isTrivial": true,
// CHECK-NEXT "isTriviallyCopyable": true,
// CHECK-NEXT "moveAssign": {},
template struct CopyAssignmentCheck<2>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "canPassInRegisters": true,
// CHECK-NEXT "copyAssign": {
// CHECK-NEXT "hasConstParam": true,
// CHECK-NEXT "implicitHasConstParam": true,
// CHECK-NEXT "trivial": true,
// CHECK-NEXT "userDeclared": true
// CHECK-NEXT },
// CHECK: "hasConstexprNonCopyMoveConstructor": true,
// CHECK-NEXT "isAggregate": true,
// CHECK-NEXT "isEmpty": true,
// CHECK-NEXT "isLiteral": true,
// CHECK-NEXT "isStandardLayout": true,
// CHECK-NEXT "isTrivial": true,
// CHECK-NEXT "isTriviallyCopyable": true,
// CHECK-NEXT "moveAssign": {},
template struct CopyAssignmentCheck<3>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "canPassInRegisters": true,
// CHECK-NEXT "copyAssign": {
// CHECK-NEXT "hasConstParam": true,
// CHECK-NEXT "implicitHasConstParam": true,
// CHECK-NEXT "trivial": true,
// CHECK-NEXT "userDeclared": true
// CHECK-NEXT },
// CHECK: "hasConstexprNonCopyMoveConstructor": true,
// CHECK-NEXT "isAggregate": true,
// CHECK-NEXT "isEmpty": true,
// CHECK-NEXT "isLiteral": true,
// CHECK-NEXT "isStandardLayout": true,
// CHECK-NEXT "moveAssign": {},
template <int N>
struct MoveAssignmentCheck {
MoveAssignmentCheck& operator=(MoveAssignmentCheck&&) requires(N == 1) = default;
MoveAssignmentCheck& operator=(MoveAssignmentCheck&&) requires(N == 2) = delete;
MoveAssignmentCheck& operator=(MoveAssignmentCheck&&) requires(N == 3);
MoveAssignmentCheck& operator=(MoveAssignmentCheck&&);
};
template struct MoveAssignmentCheck<1>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "hasConstexprNonCopyMoveConstructor": true,
// CHECK-NEXT: "isAggregate": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isLiteral": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "isTrivial": true,
// CHECK-NEXT: "isTriviallyCopyable": true,
// CHECK-NEXT: "moveAssign": {
// CHECK-NEXT: "exists": true,
// CHECK-NEXT: "trivial": true,
// CHECK-NEXT: "userDeclared": true
// CHECK-NEXT: },
template struct MoveAssignmentCheck<2>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "hasConstexprNonCopyMoveConstructor": true,
// CHECK-NEXT: "isAggregate": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isLiteral": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "isTrivial": true,
// CHECK-NEXT: "isTriviallyCopyable": true,
// CHECK-NEXT: "moveAssign": {
// CHECK-NEXT: "exists": true,
// CHECK-NEXT: "trivial": true,
// CHECK-NEXT: "userDeclared": true
// CHECK-NEXT: },
template struct MoveAssignmentCheck<3>;
// CHECK: "kind": "ClassTemplateSpecializationDecl",
// CHECK: "definitionData": {
// CHECK-NEXT: "canConstDefaultInit": true,
// CHECK-NEXT: "copyAssign": {
// CHECK: "hasConstexprNonCopyMoveConstructor": true,
// CHECK-NEXT: "isAggregate": true,
// CHECK-NEXT: "isEmpty": true,
// CHECK-NEXT: "isLiteral": true,
// CHECK-NEXT: "isStandardLayout": true,
// CHECK-NEXT: "moveAssign": {
// CHECK-NEXT: "exists": true,
// CHECK-NEXT: "nonTrivial": true,
// CHECK-NEXT: "userDeclared": true
// CHECK-NEXT: },

View File

@ -0,0 +1,231 @@
// RUN: %clang_cc1 -verify -std=c++20 %s
template <int N>
concept C0 = (N == 0);
template <int N>
concept C1 = (N == 1);
template <int N>
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 <int N>
struct DefaultConstructorChecker {
DefaultConstructorChecker() requires C0<N> = default;
DefaultConstructorChecker() requires C1<N> = delete;
DefaultConstructorChecker() requires C2<N>;
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 <int N>
struct CopyConstructorChecker {
CopyConstructorChecker(const CopyConstructorChecker&) requires C0<N> = default;
CopyConstructorChecker(const CopyConstructorChecker&) requires C1<N> = delete;
CopyConstructorChecker(const CopyConstructorChecker&) requires C2<N>;
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 <int N>
struct MoveConstructorChecker {
MoveConstructorChecker(MoveConstructorChecker&&) requires C0<N> = default;
MoveConstructorChecker(MoveConstructorChecker&&) requires C1<N> = delete;
MoveConstructorChecker(MoveConstructorChecker&&) requires C2<N>;
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 <int N>
struct MoveAssignmentChecker {
MoveAssignmentChecker& operator=(MoveAssignmentChecker&&) requires C0<N> = default;
MoveAssignmentChecker& operator=(MoveAssignmentChecker&&) requires C1<N> = delete;
MoveAssignmentChecker& operator=(MoveAssignmentChecker&&) requires C2<N>;
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 <int N>
struct CopyAssignmentChecker {
CopyAssignmentChecker& operator=(const CopyAssignmentChecker&) requires C0<N> = default;
CopyAssignmentChecker& operator=(const CopyAssignmentChecker&) requires C1<N> = delete;
CopyAssignmentChecker& operator=(const CopyAssignmentChecker&) requires C2<N>;
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 <int N>
struct KindComparisonChecker1 {
KindComparisonChecker1& operator=(const KindComparisonChecker1&) requires C0<N> = default;
KindComparisonChecker1& operator=(KindComparisonChecker1&);
};
template <int N>
struct KindComparisonChecker2 {
KindComparisonChecker2& operator=(const KindComparisonChecker2&) requires C0<N> = default;
const KindComparisonChecker2& operator=(KindComparisonChecker2&) const;
};
template <int N>
struct KindComparisonChecker3 {
using Alias = KindComparisonChecker3;
Alias& operator=(const Alias&) requires C0<N> = default;
KindComparisonChecker3& operator=(const KindComparisonChecker3&);
};
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>));
static_assert(__is_trivial(KindComparisonChecker3<0>));
static_assert(__is_trivially_copyable(KindComparisonChecker3<0>));
template <class T>
concept HasA = requires(T t) {
{ t.a() };
};
template <class T>
concept HasAB = HasA<T> && requires(T t) {
{ t.b() };
};
template <class T>
concept HasAC = HasA<T> && requires(T t) {
{ t.c() };
};
template <class T>
concept HasABC = HasAB<T> && HasAC<T> && requires(T t) {
{ t.c() };
};
template <class T>
struct ComplexConstraints {
ComplexConstraints() requires HasABC<T> = default;
ComplexConstraints() requires HasAB<T>;
ComplexConstraints() requires HasAC<T>;
ComplexConstraints() requires HasA<T> = delete;
ComplexConstraints();
};
struct A {
void a();
};
struct AB {
void a();
void b();
};
struct ABC {
void a();
void b();
void c();
};
struct AC {
void a();
void c();
};
static_assert(__is_trivial(ComplexConstraints<ABC>), "");
static_assert(!__is_trivial(ComplexConstraints<AB>), "");
static_assert(!__is_trivial(ComplexConstraints<AC>), "");
static_assert(__is_trivial(ComplexConstraints<A>), "");
static_assert(!__is_trivial(ComplexConstraints<int>), "");
// This is evaluated at the completion of CRTPBase, while `T` is not yet completed.
// This is probably correct behavior.
// FIXME: We should not throw an error, instead SFINAE should make the constraint
// silently unsatisfied. See [temp.constr.constr]p5
template <class T>
struct CRTPBase {
CRTPBase() requires (sizeof(T) > 0); // expected-error {{to an incomplete type}}
CRTPBase() = default;
};
struct Child : CRTPBase<Child> { int x; };
// expected-note@-1 {{definition of 'Child' is not complete until}}
// expected-note@-2 {{in instantiation of template class 'CRTPBase<Child>' requested here}}
static Child c;
namespace GH57046 {
template<unsigned N>
struct Foo {
Foo() requires (N==1) {} // expected-note {{declared here}}
Foo() requires (N==2) = default;
};
template <unsigned N, unsigned M>
struct S {
Foo<M> data;
S() requires (N==1) {}
consteval S() requires (N==2) = default; // expected-note {{non-constexpr constructor 'Foo' cannot be used in a constant expression}}
};
void func() {
S<2, 1> s1; // expected-error {{is not a constant expression}} expected-note {{in call to 'S()'}}
S<2, 2> s2;
}
}

View File

@ -927,8 +927,16 @@ code. This issue is expected to be rectified soon.
</tr>
<tr> <!-- from Cologne -->
<td><a href="https://wg21.link/p0848r3">P0848R3</a></td>
<td rowspan="1" class="none" align="center">No</td>
</tr>
<td rowspan="1" class="unreleased" align="center">
<details>
<summary>Clang 16 (Partial)</summary>
Because of other concepts implementation deficits, the __cpp_concepts macro is not yet set to 202002L.
Also, the related defect reports <a href="https://wg21.link/cwg1496">DR1496</a> and
<a href="https://wg21.link/cwg1734">DR1734</a> are not yet implemented. Accordingly, deleted
special member functions are treated as eligible even though they shouldn't be.
</details>
</td>
</tr>
<tr>
<td><a href="https://wg21.link/p1616r1">P1616R1</a></td>
<td rowspan="2" class="full" align="center">Clang 10</td>