https://github.com/zwuis updated https://github.com/llvm/llvm-project/pull/193754
>From a868262385c2d2594a44be3b9ccab1de64a820d0 Mon Sep 17 00:00:00 2001 From: Yanzuo Liu <[email protected]> Date: Thu, 23 Apr 2026 21:48:57 +0800 Subject: [PATCH 01/10] Reject template arguments not equivalent to their copies (part of P2308R1) --- clang/docs/ReleaseNotes.rst | 2 + .../clang/Basic/DiagnosticSemaKinds.td | 5 ++ clang/lib/AST/ASTContext.cpp | 2 + clang/lib/Sema/SemaTemplate.cpp | 86 +++++++++++++++++-- .../SemaTemplate/temp_arg_nontype_cxx20.cpp | 57 ++++++++++-- 5 files changed, 134 insertions(+), 18 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 03362cf4e0f8a..957caabc89845 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -49,6 +49,8 @@ C++ Specific Potentially Breaking Changes - Clang now correctly rejects ``export`` declarations in module implementation partitions. (#GH107602) +- Invalid template arguments which aren't equivalent to their copies are correctly rejected. + ABI Changes in This Version --------------------------- diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index cc82bb6a51e52..fe12b2ed39591 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5749,8 +5749,13 @@ def err_template_arg_not_object_or_func : Error< "non-type template argument does not refer to an object or function">; def err_template_arg_not_pointer_to_member_form : Error< "non-type template argument is not a pointer to member constant">; +def err_template_arg_not_equivalent_to_its_copy : Error< + "non-type template argument is not equivalent to its copy">; +def note_template_arg_requires_copy : Note< + "non-type template argument is required to be copyable">; def err_template_arg_invalid : Error< "non-type template argument '%0' is invalid">; + def err_pointer_to_member_type : Error< "invalid use of pointer to member type after %select{.*|->*}0">; def err_pointer_to_member_call_drops_quals : Error< diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index abf5f8a832043..e520ef7eec0ce 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13793,6 +13793,8 @@ ASTContext::getUnnamedGlobalConstantDecl(QualType Ty, TemplateParamObjectDecl * ASTContext::getTemplateParamObjectDecl(QualType T, const APValue &V) const { assert(T->isRecordType() && "template param object of unexpected type"); + assert(!T->isInstantiationDependentType() && + "instantiation-dependent types are not supported"); // C++ [temp.param]p8: // [...] a static storage duration object of type 'const T' [...] diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 7b7d43ef3234c..f468967abc89a 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -7130,6 +7130,72 @@ static bool CheckTemplateArgumentPointerToMember( return true; } +// P2308R1 C++26 [temp.arg.nontype]p4: +// ... If, for the initialization from any candidate initializer, +// - ... +// - the initialization would cause P to not be +// template-argument-equivalent ([temp.type]) to v, +// the program is ill-formed. +// TODO: Cache accepted APValue for performance improvement. +static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, + QualType ParamType, + const APValue &Value, + SourceLocation ArgLoc) { + assert(ParamType->isRecordType() && "no need to check copy equivalence"); + + SourceLocation ParamLoc = Param->getLocation(); + + // Instead of creating a variable (C++26 [temp.arg.nontype]p3), + // create a template parameter object to represent the candidate initializer. + // They are equivalent during creating a copy. + auto *CandidateInitializer = + S.BuildDeclRefExpr(S.Context.getTemplateParamObjectDecl(ParamType, Value), + ParamType.withConst(), VK_LValue, ArgLoc); + InitializationKind Kind = InitializationKind::CreateForInit( + ArgLoc, /*DirectInit=*/false, CandidateInitializer); + Expr *Inits[1] = {CandidateInitializer}; + InitializedEntity Entity = + InitializedEntity::InitializeTemplateParameter(ParamType, Param); + InitializationSequence InitSeq(S, Entity, Kind, Inits); + ExprResult Result = InitSeq.Perform(S, Entity, Kind, Inits); + if (Result.isInvalid()) { + S.Diag(ParamLoc, diag::note_template_arg_requires_copy); + return true; + } + + // Fast path. Trivial copy constructor performs per-element copy. + if (cast<CXXConstructExpr>(Result.get())->getConstructor()->isTrivial()) + return false; + + Result = S.ActOnConstantExpression(Result.get()); + Result = S.ActOnFinishFullExpr(AssertSuccess(Result), ArgLoc, + /*DiscardedValue=*/false, + /*IsConstexpr=*/true, + /*IsTemplateArgument=*/true); + + APValue ValueAfterCopy, PreNarrowingValue; + Result = S.EvaluateConvertedConstantExpression( + AssertSuccess(Result), ParamType, ValueAfterCopy, CCEKind::TemplateArg, + /*RequireInt=*/false, PreNarrowingValue); + if (Result.isInvalid()) { + S.Diag(ParamLoc, diag::note_template_arg_requires_copy); + return true; + } + + llvm::FoldingSetNodeID VID, CID; + Value.Profile(VID); + ValueAfterCopy.Profile(CID); + if (VID == CID) + return false; + + S.Diag(ArgLoc, diag::err_template_arg_not_equivalent_to_its_copy); + if (Param->getDeclName()) + S.Diag(ParamLoc, diag::note_parameter_named_here) << Param->getDeclName(); + else + S.Diag(ParamLoc, diag::note_parameter_here); + return true; +} + /// Check a template argument against its corresponding /// non-type template parameter. /// @@ -7272,14 +7338,10 @@ ExprResult Sema::CheckTemplateArgument(NamedDecl *Param, QualType ParamType, return Arg; } - // Avoid making a copy when initializing a template parameter of class type - // from a template parameter object of the same type. This is going beyond - // the standard, but is required for soundness: in - // template<A a> struct X { X *p; X<a> *q; }; - // ... we need p and q to have the same type. - // - // Similarly, don't inject a call to a copy constructor when initializing - // from a template parameter of the same type. + // Fast path. A valid template parameter object is equivalent to its copy. No + // need to make a copy to check again when initializing a template parameter + // of class type from a template parameter object of the same type. + // TODO: Remove `ParamType->isRecordType()` in condition? Expr *InnerArg = DeductionArg->IgnoreParenImpCasts(); if (ParamType->isRecordType() && isa<DeclRefExpr>(InnerArg) && Context.hasSameUnqualifiedType(ParamType, InnerArg->getType())) { @@ -7417,6 +7479,12 @@ ExprResult Sema::CheckTemplateArgument(NamedDecl *Param, QualType ParamType, if (Value.isAddrLabelDiff()) return Diag(StartLoc, diag::err_non_type_template_arg_addr_label_diff); + if (ParamType->isRecordType() && + !ParamType->isInstantiationDependentType() && + CheckTemplateArgumentCopyEquivalence(*this, Param, ParamType, Value, + StartLoc)) + return ExprError(); + if (ArgPE) { SugaredConverted = TemplateArgument(Arg, /*IsCanonical=*/false); CanonicalConverted = @@ -7430,7 +7498,7 @@ ExprResult Sema::CheckTemplateArgument(NamedDecl *Param, QualType ParamType, } // These should have all been handled above using the C++17 rules. - assert(!ArgPE && !StrictCheck); + assert(!ArgPE && !StrictCheck && !ParamType->isRecordType()); // C++ [temp.arg.nontype]p5: // The following conversions are performed on each expression used diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp index 8450ff037e184..463eb714fa675 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp @@ -106,30 +106,69 @@ namespace ConvertedConstant { } namespace CopyCounting { - // Make sure we don't use the copy constructor when transferring the "same" - // template parameter object around. struct A { int n; constexpr A(int n = 0) : n(n) {} constexpr A(const A &a) : n(a.n+1) {} }; - template<A a> struct X {}; + template<A a> struct X {}; // expected-note {{passing argument to parameter 'a' here}} template<A a> constexpr int f(X<a> x) { return a.n; } - static_assert(f(X<A{}>()) == 0); + static_assert(f(X<A{}>()) == 0); // expected-error {{non-type template argument is not equivalent to its copy}} - template<A a> struct Y { void f(); }; + template<A a> struct Y { void f(); }; // expected-note {{passing argument to parameter 'a' here}} template<A a> void g(Y<a> y) { y.Y<a>::f(); } - void h() { constexpr A a; g<a>(Y<a>{}); } + void h() { constexpr A a; g<a>(Y<a>{}); } // expected-error {{non-type template argument is not equivalent to its copy}} - template<A a> struct Z { + template<A a> struct Z { // expected-note 2 {{passing argument to parameter 'a' here}} constexpr int f() { constexpr A v = a; // this is {a.n+1} return Z<v>().f() + 1; // this is Z<{a.n+2}> } }; - template<> struct Z<A{20}> { + template<> struct Z<A{20}> { // expected-error {{non-type template argument is not equivalent to its copy}} constexpr int f() { return 32; } }; - static_assert(Z<A{}>().f() == 42); + static_assert(Z<A{}>().f() == 42); // expected-error {{non-type template argument is not equivalent to its copy}} +} + +namespace ConditionallyModify { +struct A { + int x; + constexpr A(int x) : x(x) {} + constexpr A(const A& a) : x(a.x + (a.x > 1)) {} +}; +template <A a> struct X {}; // expected-note {{passing argument to parameter 'a' here}} +template struct X<1>; +template struct X<2>; // expected-error {{non-type template argument is not equivalent to its copy}} +} + +namespace CannotCopy { +template <typename T, template <T> typename TEMPLATE> +concept C = (TEMPLATE<{}>{}, true); + +struct S1 { + constexpr S1() = default; // expected-note {{candidate constructor not viable: requires 0 arguments, but 1 was provided}} + constexpr S1(S1&) = default; // expected-note {{candidate constructor not viable: 1st argument ('const S1') would lose const qualifier}} +}; +template <S1> struct T1 {}; // expected-note {{passing argument to parameter here}} expected-note {{non-type template argument is required to be copyable}} +template struct T1<{}>; // expected-error {{no matching constructor for initialization of 'S1'}} +static_assert(!C<S1, T1>); + +struct S2 { + constexpr S2() = default; // expected-note {{candidate constructor not viable: requires 0 arguments, but 1 was provided}} + explicit constexpr S2(const S2&) = default; // expected-note {{explicit constructor is not a candidate}} +}; +template <S2> struct T2 {}; // expected-note {{passing argument to parameter here}} expected-note {{non-type template argument is required to be copyable}} +template struct T2<{}>; // expected-error {{no matching constructor for initialization of 'S2'}} +static_assert(!C<S2, T2>); + +struct S3 { + constexpr S3() = default; + S3(const S3&) {} // expected-note {{declared here}} +}; +template <S3> struct T3 {}; // expected-note {{non-type template argument is required to be copyable}} +template struct T3<{}>; // expected-error {{non-type template argument is not a constant expression}} \ + expected-note {{non-constexpr constructor 'S3' cannot be used in a constant expression}} +static_assert(!C<S3, T3>); } namespace StableAddress { >From b976a63f20928f8d1506e69847620cfc6d16785a Mon Sep 17 00:00:00 2001 From: Yanzuo Liu <[email protected]> Date: Sun, 26 Apr 2026 12:22:52 +0800 Subject: [PATCH 02/10] Style and performance improvement --- clang/include/clang/AST/DeclCXX.h | 13 ++++ .../clang/Basic/DiagnosticSemaKinds.td | 1 - clang/lib/AST/DeclCXX.cpp | 3 +- clang/lib/Sema/SemaTemplate.cpp | 22 +++--- .../SemaTemplate/temp_arg_nontype_cxx20.cpp | 69 +++++++++++++------ 5 files changed, 73 insertions(+), 35 deletions(-) diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index 2af396f025c93..c90d3e74912eb 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -310,6 +310,11 @@ class CXXRecordDecl : public RecordDecl { LLVM_PREFERRED_TYPE(bool) unsigned HasODRHash : 1; + /// True if copying a template parameter (C++26 [temp.arg.nontype]p4) of + /// this type uses trivial copy constructor. + LLVM_PREFERRED_TYPE(bool) + unsigned TriviallyCopyTemplateParam : 1; + /// A hash of parts of the class to help in ODR checking. unsigned ODRHash = 0; @@ -595,6 +600,14 @@ class CXXRecordDecl : public RecordDecl { unsigned getODRHash() const; + void setTriviallyCopyTemplateParam() { + data().TriviallyCopyTemplateParam = true; + } + + bool isTriviallyCopyTemplateParam() const { + return data().TriviallyCopyTemplateParam; + } + /// Sets the base classes of this struct or class. void setBases(CXXBaseSpecifier const * const *Bases, unsigned NumBases); diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index fe12b2ed39591..3b47fa7a1ded3 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -5755,7 +5755,6 @@ def note_template_arg_requires_copy : Note< "non-type template argument is required to be copyable">; def err_template_arg_invalid : Error< "non-type template argument '%0' is invalid">; - def err_pointer_to_member_type : Error< "invalid use of pointer to member type after %select{.*|->*}0">; def err_pointer_to_member_call_drops_quals : Error< diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 3b9d888bb2c0a..742936da909d5 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -111,7 +111,8 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D) HasDeclaredCopyAssignmentWithConstParam(false), IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsPFPType(false), IsLambda(false), IsParsingBaseSpecifiers(false), - ComputedVisibleConversions(false), HasODRHash(false), Definition(D) {} + ComputedVisibleConversions(false), HasODRHash(false), + TriviallyCopyTemplateParam(false), Definition(D) {} CXXBaseSpecifier *CXXRecordDecl::DefinitionData::getBasesSlowCase() const { return Bases.get(Definition->getASTContext().getExternalSource()); diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index f468967abc89a..926e74b25fa08 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -7136,13 +7136,15 @@ static bool CheckTemplateArgumentPointerToMember( // - the initialization would cause P to not be // template-argument-equivalent ([temp.type]) to v, // the program is ill-formed. -// TODO: Cache accepted APValue for performance improvement. static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, QualType ParamType, const APValue &Value, SourceLocation ArgLoc) { assert(ParamType->isRecordType() && "no need to check copy equivalence"); + if (ParamType->castAsCXXRecordDecl()->isTriviallyCopyTemplateParam()) + return false; + SourceLocation ParamLoc = Param->getLocation(); // Instead of creating a variable (C++26 [temp.arg.nontype]p3), @@ -7158,14 +7160,13 @@ static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, InitializedEntity::InitializeTemplateParameter(ParamType, Param); InitializationSequence InitSeq(S, Entity, Kind, Inits); ExprResult Result = InitSeq.Perform(S, Entity, Kind, Inits); - if (Result.isInvalid()) { - S.Diag(ParamLoc, diag::note_template_arg_requires_copy); - return true; - } + if (Result.isInvalid()) + return S.Diag(ParamLoc, diag::note_template_arg_requires_copy); - // Fast path. Trivial copy constructor performs per-element copy. - if (cast<CXXConstructExpr>(Result.get())->getConstructor()->isTrivial()) + if (cast<CXXConstructExpr>(Result.get())->getConstructor()->isTrivial()) { + ParamType->castAsCXXRecordDecl()->setTriviallyCopyTemplateParam(); return false; + } Result = S.ActOnConstantExpression(Result.get()); Result = S.ActOnFinishFullExpr(AssertSuccess(Result), ArgLoc, @@ -7177,10 +7178,8 @@ static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, Result = S.EvaluateConvertedConstantExpression( AssertSuccess(Result), ParamType, ValueAfterCopy, CCEKind::TemplateArg, /*RequireInt=*/false, PreNarrowingValue); - if (Result.isInvalid()) { - S.Diag(ParamLoc, diag::note_template_arg_requires_copy); - return true; - } + if (Result.isInvalid()) + return S.Diag(ParamLoc, diag::note_template_arg_requires_copy); llvm::FoldingSetNodeID VID, CID; Value.Profile(VID); @@ -7341,7 +7340,6 @@ ExprResult Sema::CheckTemplateArgument(NamedDecl *Param, QualType ParamType, // Fast path. A valid template parameter object is equivalent to its copy. No // need to make a copy to check again when initializing a template parameter // of class type from a template parameter object of the same type. - // TODO: Remove `ParamType->isRecordType()` in condition? Expr *InnerArg = DeductionArg->IgnoreParenImpCasts(); if (ParamType->isRecordType() && isa<DeclRefExpr>(InnerArg) && Context.hasSameUnqualifiedType(ParamType, InnerArg->getType())) { diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp index 463eb714fa675..cb09c6dc7115d 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp @@ -107,27 +107,35 @@ namespace ConvertedConstant { namespace CopyCounting { struct A { int n; constexpr A(int n = 0) : n(n) {} constexpr A(const A &a) : n(a.n+1) {} }; - template<A a> struct X {}; // expected-note {{passing argument to parameter 'a' here}} + template<A a> struct X {}; // #CopyCounting-X template<A a> constexpr int f(X<a> x) { return a.n; } - static_assert(f(X<A{}>()) == 0); // expected-error {{non-type template argument is not equivalent to its copy}} + static_assert(f(X<A{}>()) == 0); + // expected-error@-1 {{non-type template argument is not equivalent to its copy}} + // expected-note@#CopyCounting-X {{passing argument to parameter 'a' here}} - template<A a> struct Y { void f(); }; // expected-note {{passing argument to parameter 'a' here}} + template<A a> struct Y { void f(); }; // #CopyCounting-Y template<A a> void g(Y<a> y) { y.Y<a>::f(); } - void h() { constexpr A a; g<a>(Y<a>{}); } // expected-error {{non-type template argument is not equivalent to its copy}} + void h() { constexpr A a; g<a>(Y<a>{}); } + // expected-error@-1 {{non-type template argument is not equivalent to its copy}} + // expected-note@#CopyCounting-Y {{passing argument to parameter 'a' here}} - template<A a> struct Z { // expected-note 2 {{passing argument to parameter 'a' here}} + template<A a> struct Z { // #CopyCounting-Z constexpr int f() { constexpr A v = a; // this is {a.n+1} return Z<v>().f() + 1; // this is Z<{a.n+2}> } }; - template<> struct Z<A{20}> { // expected-error {{non-type template argument is not equivalent to its copy}} + template<> struct Z<A{20}> { + // expected-error@-1 {{non-type template argument is not equivalent to its copy}} + // expected-note@#CopyCounting-Z {{passing argument to parameter 'a' here}} constexpr int f() { return 32; } }; - static_assert(Z<A{}>().f() == 42); // expected-error {{non-type template argument is not equivalent to its copy}} + static_assert(Z<A{}>().f() == 42); + // expected-error@-1 {{non-type template argument is not equivalent to its copy}} + // expected-note@#CopyCounting-Z {{passing argument to parameter 'a' here}} } namespace ConditionallyModify { @@ -136,9 +144,12 @@ struct A { constexpr A(int x) : x(x) {} constexpr A(const A& a) : x(a.x + (a.x > 1)) {} }; -template <A a> struct X {}; // expected-note {{passing argument to parameter 'a' here}} +template <A a> // #ConditionallyModify-a +struct X {}; template struct X<1>; -template struct X<2>; // expected-error {{non-type template argument is not equivalent to its copy}} +template struct X<2>; +// expected-error@-1 {{non-type template argument is not equivalent to its copy}} +// expected-note@#ConditionallyModify-a {{passing argument to parameter 'a' here}} } namespace CannotCopy { @@ -146,28 +157,44 @@ template <typename T, template <T> typename TEMPLATE> concept C = (TEMPLATE<{}>{}, true); struct S1 { - constexpr S1() = default; // expected-note {{candidate constructor not viable: requires 0 arguments, but 1 was provided}} - constexpr S1(S1&) = default; // expected-note {{candidate constructor not viable: 1st argument ('const S1') would lose const qualifier}} + constexpr S1() = default; // #CannotCopy-S1-default-ctor + constexpr S1(S1&) = default; // #CannotCopy-S1-copy-ctor }; -template <S1> struct T1 {}; // expected-note {{passing argument to parameter here}} expected-note {{non-type template argument is required to be copyable}} -template struct T1<{}>; // expected-error {{no matching constructor for initialization of 'S1'}} +template <S1> // #CannotCopy-T1-S1 +struct T1 {}; +template struct T1<{}>; +// expected-error@-1 {{no matching constructor for initialization of 'S1'}} +// expected-note@#CannotCopy-S1-copy-ctor {{candidate constructor not viable: 1st argument ('const S1') would lose const qualifier}} +// expected-note@#CannotCopy-S1-default-ctor {{candidate constructor not viable: requires 0 arguments, but 1 was provided}} +// expected-note@#CannotCopy-T1-S1 {{passing argument to parameter here}} +// expected-note@#CannotCopy-T1-S1 {{non-type template argument is required to be copyable}} static_assert(!C<S1, T1>); struct S2 { - constexpr S2() = default; // expected-note {{candidate constructor not viable: requires 0 arguments, but 1 was provided}} - explicit constexpr S2(const S2&) = default; // expected-note {{explicit constructor is not a candidate}} + constexpr S2() = default; // #CannotCopy-S2-default-ctor + explicit constexpr S2(const S2&) = default; // #CannotCopy-S2-copy-ctor }; -template <S2> struct T2 {}; // expected-note {{passing argument to parameter here}} expected-note {{non-type template argument is required to be copyable}} -template struct T2<{}>; // expected-error {{no matching constructor for initialization of 'S2'}} +template <S2> // #CannotCopy-T2-S2 +struct T2 {}; +template struct T2<{}>; +// expected-error@-1 {{no matching constructor for initialization of 'S2'}} +// expected-note@#CannotCopy-S2-copy-ctor {{explicit constructor is not a candidate}} +// expected-note@#CannotCopy-S2-default-ctor {{candidate constructor not viable: requires 0 arguments, but 1 was provided}} +// expected-note@#CannotCopy-T2-S2 {{passing argument to parameter here}} +// expected-note@#CannotCopy-T2-S2 {{non-type template argument is required to be copyable}} static_assert(!C<S2, T2>); struct S3 { constexpr S3() = default; - S3(const S3&) {} // expected-note {{declared here}} + S3(const S3&) {} // #CannotCopy-S3-copy-ctor }; -template <S3> struct T3 {}; // expected-note {{non-type template argument is required to be copyable}} -template struct T3<{}>; // expected-error {{non-type template argument is not a constant expression}} \ - expected-note {{non-constexpr constructor 'S3' cannot be used in a constant expression}} +template <S3> // #CannotCopy-T3-S3 +struct T3 {}; +template struct T3<{}>; +// expected-error@-1 {{non-type template argument is not a constant expression}} +// expected-note@-2 {{non-constexpr constructor 'S3' cannot be used in a constant expression}} +// expected-note@#CannotCopy-S3-copy-ctor {{declared here}} +// expected-note@#CannotCopy-T3-S3 {{non-type template argument is required to be copyable}} static_assert(!C<S3, T3>); } >From 6f26dac62959dceae70643af0a98dd5bbc3a8bcc Mon Sep 17 00:00:00 2001 From: Yanzuo Liu <[email protected]> Date: Mon, 27 Apr 2026 22:56:17 +0800 Subject: [PATCH 03/10] Update clang/lib/Sema/SemaTemplate.cpp Co-authored-by: Corentin Jabot <[email protected]> --- clang/lib/Sema/SemaTemplate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 926e74b25fa08..145a334466110 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -7149,7 +7149,7 @@ static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, // Instead of creating a variable (C++26 [temp.arg.nontype]p3), // create a template parameter object to represent the candidate initializer. - // They are equivalent during creating a copy. + // They are equivalent when creating a copy. auto *CandidateInitializer = S.BuildDeclRefExpr(S.Context.getTemplateParamObjectDecl(ParamType, Value), ParamType.withConst(), VK_LValue, ArgLoc); >From b36b29b897b3eea0850f7bf95ed712e8f1948ab4 Mon Sep 17 00:00:00 2001 From: Yanzuo Liu <[email protected]> Date: Mon, 27 Apr 2026 23:22:55 +0800 Subject: [PATCH 04/10] Improve readability in tests. --- .../SemaTemplate/temp_arg_nontype_cxx20.cpp | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp index cb09c6dc7115d..a10a633dfc688 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp @@ -112,13 +112,13 @@ namespace CopyCounting { static_assert(f(X<A{}>()) == 0); // expected-error@-1 {{non-type template argument is not equivalent to its copy}} - // expected-note@#CopyCounting-X {{passing argument to parameter 'a' here}} + // expected-note@#CopyCounting-X {{passing argument to parameter 'a' here}} template<A a> struct Y { void f(); }; // #CopyCounting-Y template<A a> void g(Y<a> y) { y.Y<a>::f(); } void h() { constexpr A a; g<a>(Y<a>{}); } // expected-error@-1 {{non-type template argument is not equivalent to its copy}} - // expected-note@#CopyCounting-Y {{passing argument to parameter 'a' here}} + // expected-note@#CopyCounting-Y {{passing argument to parameter 'a' here}} template<A a> struct Z { // #CopyCounting-Z constexpr int f() { @@ -128,14 +128,14 @@ namespace CopyCounting { }; template<> struct Z<A{20}> { // expected-error@-1 {{non-type template argument is not equivalent to its copy}} - // expected-note@#CopyCounting-Z {{passing argument to parameter 'a' here}} + // expected-note@#CopyCounting-Z {{passing argument to parameter 'a' here}} constexpr int f() { return 32; } }; static_assert(Z<A{}>().f() == 42); // expected-error@-1 {{non-type template argument is not equivalent to its copy}} - // expected-note@#CopyCounting-Z {{passing argument to parameter 'a' here}} + // expected-note@#CopyCounting-Z {{passing argument to parameter 'a' here}} } namespace ConditionallyModify { @@ -149,7 +149,7 @@ struct X {}; template struct X<1>; template struct X<2>; // expected-error@-1 {{non-type template argument is not equivalent to its copy}} -// expected-note@#ConditionallyModify-a {{passing argument to parameter 'a' here}} +// expected-note@#ConditionallyModify-a {{passing argument to parameter 'a' here}} } namespace CannotCopy { @@ -164,10 +164,10 @@ template <S1> // #CannotCopy-T1-S1 struct T1 {}; template struct T1<{}>; // expected-error@-1 {{no matching constructor for initialization of 'S1'}} -// expected-note@#CannotCopy-S1-copy-ctor {{candidate constructor not viable: 1st argument ('const S1') would lose const qualifier}} -// expected-note@#CannotCopy-S1-default-ctor {{candidate constructor not viable: requires 0 arguments, but 1 was provided}} -// expected-note@#CannotCopy-T1-S1 {{passing argument to parameter here}} -// expected-note@#CannotCopy-T1-S1 {{non-type template argument is required to be copyable}} +// expected-note@#CannotCopy-S1-copy-ctor {{candidate constructor not viable: 1st argument ('const S1') would lose const qualifier}} +// expected-note@#CannotCopy-S1-default-ctor {{candidate constructor not viable: requires 0 arguments, but 1 was provided}} +// expected-note@#CannotCopy-T1-S1 {{passing argument to parameter here}} +// expected-note@#CannotCopy-T1-S1 {{non-type template argument is required to be copyable}} static_assert(!C<S1, T1>); struct S2 { @@ -178,10 +178,10 @@ template <S2> // #CannotCopy-T2-S2 struct T2 {}; template struct T2<{}>; // expected-error@-1 {{no matching constructor for initialization of 'S2'}} -// expected-note@#CannotCopy-S2-copy-ctor {{explicit constructor is not a candidate}} -// expected-note@#CannotCopy-S2-default-ctor {{candidate constructor not viable: requires 0 arguments, but 1 was provided}} -// expected-note@#CannotCopy-T2-S2 {{passing argument to parameter here}} -// expected-note@#CannotCopy-T2-S2 {{non-type template argument is required to be copyable}} +// expected-note@#CannotCopy-S2-copy-ctor {{explicit constructor is not a candidate}} +// expected-note@#CannotCopy-S2-default-ctor {{candidate constructor not viable: requires 0 arguments, but 1 was provided}} +// expected-note@#CannotCopy-T2-S2 {{passing argument to parameter here}} +// expected-note@#CannotCopy-T2-S2 {{non-type template argument is required to be copyable}} static_assert(!C<S2, T2>); struct S3 { @@ -192,9 +192,9 @@ template <S3> // #CannotCopy-T3-S3 struct T3 {}; template struct T3<{}>; // expected-error@-1 {{non-type template argument is not a constant expression}} -// expected-note@-2 {{non-constexpr constructor 'S3' cannot be used in a constant expression}} -// expected-note@#CannotCopy-S3-copy-ctor {{declared here}} -// expected-note@#CannotCopy-T3-S3 {{non-type template argument is required to be copyable}} +// expected-note@-2 {{non-constexpr constructor 'S3' cannot be used in a constant expression}} +// expected-note@#CannotCopy-S3-copy-ctor {{declared here}} +// expected-note@#CannotCopy-T3-S3 {{non-type template argument is required to be copyable}} static_assert(!C<S3, T3>); } >From 2c62578b85f2f1526ca76dc83cf28b891f7a9e6b Mon Sep 17 00:00:00 2001 From: Yanzuo Liu <[email protected]> Date: Tue, 28 Apr 2026 21:26:01 +0800 Subject: [PATCH 05/10] Grammar fix and ambiguity avoidance --- clang/docs/ReleaseNotes.rst | 2 +- clang/include/clang/AST/DeclCXX.h | 10 +++++----- clang/lib/AST/DeclCXX.cpp | 2 +- clang/lib/Sema/SemaTemplate.cpp | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 957caabc89845..599b17b5ad2ff 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -49,7 +49,7 @@ C++ Specific Potentially Breaking Changes - Clang now correctly rejects ``export`` declarations in module implementation partitions. (#GH107602) -- Invalid template arguments which aren't equivalent to their copies are correctly rejected. +- Template arguments which aren't equivalent to their copies are correctly rejected. ABI Changes in This Version --------------------------- diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index c90d3e74912eb..b2a1010b4ebec 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -313,7 +313,7 @@ class CXXRecordDecl : public RecordDecl { /// True if copying a template parameter (C++26 [temp.arg.nontype]p4) of /// this type uses trivial copy constructor. LLVM_PREFERRED_TYPE(bool) - unsigned TriviallyCopyTemplateParam : 1; + unsigned TriviallyCopyingTemplateParam : 1; /// A hash of parts of the class to help in ODR checking. unsigned ODRHash = 0; @@ -600,12 +600,12 @@ class CXXRecordDecl : public RecordDecl { unsigned getODRHash() const; - void setTriviallyCopyTemplateParam() { - data().TriviallyCopyTemplateParam = true; + void setTriviallyCopyingTemplateParam() { + data().TriviallyCopyingTemplateParam = true; } - bool isTriviallyCopyTemplateParam() const { - return data().TriviallyCopyTemplateParam; + bool isTriviallyCopyingTemplateParam() const { + return data().TriviallyCopyingTemplateParam; } /// Sets the base classes of this struct or class. diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 742936da909d5..43923771f1832 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -112,7 +112,7 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D) IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsPFPType(false), IsLambda(false), IsParsingBaseSpecifiers(false), ComputedVisibleConversions(false), HasODRHash(false), - TriviallyCopyTemplateParam(false), Definition(D) {} + TriviallyCopyingTemplateParam(false), Definition(D) {} CXXBaseSpecifier *CXXRecordDecl::DefinitionData::getBasesSlowCase() const { return Bases.get(Definition->getASTContext().getExternalSource()); diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 145a334466110..b43b1db92852f 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -7142,7 +7142,7 @@ static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, SourceLocation ArgLoc) { assert(ParamType->isRecordType() && "no need to check copy equivalence"); - if (ParamType->castAsCXXRecordDecl()->isTriviallyCopyTemplateParam()) + if (ParamType->castAsCXXRecordDecl()->isTriviallyCopyingTemplateParam()) return false; SourceLocation ParamLoc = Param->getLocation(); @@ -7164,11 +7164,11 @@ static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, return S.Diag(ParamLoc, diag::note_template_arg_requires_copy); if (cast<CXXConstructExpr>(Result.get())->getConstructor()->isTrivial()) { - ParamType->castAsCXXRecordDecl()->setTriviallyCopyTemplateParam(); + ParamType->castAsCXXRecordDecl()->setTriviallyCopyingTemplateParam(); return false; } - Result = S.ActOnConstantExpression(Result.get()); + Result = S.ActOnConstantExpression(Result); Result = S.ActOnFinishFullExpr(AssertSuccess(Result), ArgLoc, /*DiscardedValue=*/false, /*IsConstexpr=*/true, >From aae791a17146dc2a348ee4d297cfcff4bd5414da Mon Sep 17 00:00:00 2001 From: Yanzuo Liu <[email protected]> Date: Wed, 29 Apr 2026 21:46:18 +0800 Subject: [PATCH 06/10] Try to find copy constructor without name lookup, add tests, and drop redundant constexpr in tests --- clang/include/clang/AST/DeclCXX.h | 13 ------ clang/lib/AST/DeclCXX.cpp | 3 +- clang/lib/Sema/SemaTemplate.cpp | 40 +++++++++++++++---- .../SemaTemplate/temp_arg_nontype_cxx20.cpp | 40 ++++++++++++++++--- 4 files changed, 69 insertions(+), 27 deletions(-) diff --git a/clang/include/clang/AST/DeclCXX.h b/clang/include/clang/AST/DeclCXX.h index b2a1010b4ebec..2af396f025c93 100644 --- a/clang/include/clang/AST/DeclCXX.h +++ b/clang/include/clang/AST/DeclCXX.h @@ -310,11 +310,6 @@ class CXXRecordDecl : public RecordDecl { LLVM_PREFERRED_TYPE(bool) unsigned HasODRHash : 1; - /// True if copying a template parameter (C++26 [temp.arg.nontype]p4) of - /// this type uses trivial copy constructor. - LLVM_PREFERRED_TYPE(bool) - unsigned TriviallyCopyingTemplateParam : 1; - /// A hash of parts of the class to help in ODR checking. unsigned ODRHash = 0; @@ -600,14 +595,6 @@ class CXXRecordDecl : public RecordDecl { unsigned getODRHash() const; - void setTriviallyCopyingTemplateParam() { - data().TriviallyCopyingTemplateParam = true; - } - - bool isTriviallyCopyingTemplateParam() const { - return data().TriviallyCopyingTemplateParam; - } - /// Sets the base classes of this struct or class. void setBases(CXXBaseSpecifier const * const *Bases, unsigned NumBases); diff --git a/clang/lib/AST/DeclCXX.cpp b/clang/lib/AST/DeclCXX.cpp index 43923771f1832..3b9d888bb2c0a 100644 --- a/clang/lib/AST/DeclCXX.cpp +++ b/clang/lib/AST/DeclCXX.cpp @@ -111,8 +111,7 @@ CXXRecordDecl::DefinitionData::DefinitionData(CXXRecordDecl *D) HasDeclaredCopyAssignmentWithConstParam(false), IsAnyDestructorNoReturn(false), IsHLSLIntangible(false), IsPFPType(false), IsLambda(false), IsParsingBaseSpecifiers(false), - ComputedVisibleConversions(false), HasODRHash(false), - TriviallyCopyingTemplateParam(false), Definition(D) {} + ComputedVisibleConversions(false), HasODRHash(false), Definition(D) {} CXXBaseSpecifier *CXXRecordDecl::DefinitionData::getBasesSlowCase() const { return Bases.get(Definition->getASTContext().getExternalSource()); diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index b43b1db92852f..69fa470cd8751 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -7142,8 +7142,39 @@ static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, SourceLocation ArgLoc) { assert(ParamType->isRecordType() && "no need to check copy equivalence"); - if (ParamType->castAsCXXRecordDecl()->isTriviallyCopyingTemplateParam()) - return false; + // Fast path. Try to find the copy constructor which will be selected by + // overload resolution. Trivial copy constructor performs per-element copy. + if (auto *CXXRecord = ParamType->castAsCXXRecordDecl(); + !CXXRecord->hasUserDeclaredConstructor()) { + + if (CXXRecord->hasTrivialCopyConstructor() && + CXXRecord->implicitCopyConstructorHasConstParam() && + !CXXRecord->needsOverloadResolutionForCopyConstructor()) + return false; + + } else { + // If there is a copy constructor with const parameter and without explicit, + // it should be selected by overload resolution. + CXXConstructorDecl *CopyCtor = nullptr; + + // If there is trailing requires clause, let overload resolution handle it. + bool FindCopyCtorWithTrailingRequiresClause = false; + + // A template parameter with this type has been initialized, + // so implicitly-declared functions should be declared, + // and explicit(bool) should be resolved. + for (CXXConstructorDecl *Ctor : CXXRecord->ctors()) + if (unsigned Quals; Ctor->isCopyConstructor(Quals) && + (Quals & Qualifiers::Const) && !Ctor->isExplicit()) { + CopyCtor = Ctor; + if (Ctor->getTrailingRequiresClause()) + FindCopyCtorWithTrailingRequiresClause = true; + } + + if (CopyCtor && !FindCopyCtorWithTrailingRequiresClause && + CopyCtor->isTrivial() && !CopyCtor->isDeleted()) + return false; + } SourceLocation ParamLoc = Param->getLocation(); @@ -7163,11 +7194,6 @@ static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, if (Result.isInvalid()) return S.Diag(ParamLoc, diag::note_template_arg_requires_copy); - if (cast<CXXConstructExpr>(Result.get())->getConstructor()->isTrivial()) { - ParamType->castAsCXXRecordDecl()->setTriviallyCopyingTemplateParam(); - return false; - } - Result = S.ActOnConstantExpression(Result); Result = S.ActOnFinishFullExpr(AssertSuccess(Result), ArgLoc, /*DiscardedValue=*/false, diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp index a10a633dfc688..4af2931f4d9f3 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp @@ -157,8 +157,8 @@ template <typename T, template <T> typename TEMPLATE> concept C = (TEMPLATE<{}>{}, true); struct S1 { - constexpr S1() = default; // #CannotCopy-S1-default-ctor - constexpr S1(S1&) = default; // #CannotCopy-S1-copy-ctor + S1() = default; // #CannotCopy-S1-default-ctor + S1(S1&) = default; // #CannotCopy-S1-copy-ctor }; template <S1> // #CannotCopy-T1-S1 struct T1 {}; @@ -171,8 +171,8 @@ template struct T1<{}>; static_assert(!C<S1, T1>); struct S2 { - constexpr S2() = default; // #CannotCopy-S2-default-ctor - explicit constexpr S2(const S2&) = default; // #CannotCopy-S2-copy-ctor + S2() = default; // #CannotCopy-S2-default-ctor + explicit S2(const S2&) = default; // #CannotCopy-S2-copy-ctor }; template <S2> // #CannotCopy-T2-S2 struct T2 {}; @@ -185,7 +185,7 @@ template struct T2<{}>; static_assert(!C<S2, T2>); struct S3 { - constexpr S3() = default; + S3() = default; S3(const S3&) {} // #CannotCopy-S3-copy-ctor }; template <S3> // #CannotCopy-T3-S3 @@ -196,6 +196,36 @@ template struct T3<{}>; // expected-note@#CannotCopy-S3-copy-ctor {{declared here}} // expected-note@#CannotCopy-T3-S3 {{non-type template argument is required to be copyable}} static_assert(!C<S3, T3>); + +struct Base4 { + Base4() = default; + Base4(const Base4&) = delete; // #CannotCopy-Base4-copy-ctor +}; +struct S4 : Base4 {}; // #CannotCopy-S4 +template <S4> // #CannotCopy-T4-S4 +struct T4 {}; +template struct T4<{}>; +// expected-error@-1 {{call to implicitly-deleted copy constructor of 'S4'}} +// expected-note@#CannotCopy-S4 {{copy constructor of 'S4' is implicitly deleted because base class 'Base4' has a deleted copy constructor}} +// expected-note@#CannotCopy-Base4-copy-ctor {{'Base4' has been explicitly marked deleted here}} +// expected-note@#CannotCopy-T4-S4 {{passing argument to parameter here}} +// expected-note@#CannotCopy-T4-S4 {{non-type template argument is required to be copyable}} +static_assert(!C<S4, T4>); + +template <typename = void> +struct S5 { + S5() = default; + S5(const S5&) = default; + S5(const S5&) requires true = delete; // #CannotCopy-S5-copy-ctor +}; +template <S5<>> // #CannotCopy-T5-S5 +struct T5 {}; +template struct T5<{}>; +// expected-error@-1 {{call to deleted constructor of 'S5<>'}} +// expected-note@#CannotCopy-S5-copy-ctor {{'S5' has been explicitly marked deleted here}} +// expected-note@#CannotCopy-T5-S5 {{passing argument to parameter here}} +// expected-note@#CannotCopy-T5-S5 {{non-type template argument is required to be copyable}} +static_assert(!C<S5<>, T5>); } namespace StableAddress { >From 0ce43611401fe8be81bacb58c9329bc37ca16862 Mon Sep 17 00:00:00 2001 From: Yanzuo Liu <[email protected]> Date: Thu, 30 Apr 2026 11:58:57 +0800 Subject: [PATCH 07/10] Qualifier match fix It's not that important. Defaulted copy constructor with `volatile` is deleted. --- clang/lib/Sema/SemaTemplate.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 69fa470cd8751..3faefc941ba23 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -7165,7 +7165,7 @@ static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, // and explicit(bool) should be resolved. for (CXXConstructorDecl *Ctor : CXXRecord->ctors()) if (unsigned Quals; Ctor->isCopyConstructor(Quals) && - (Quals & Qualifiers::Const) && !Ctor->isExplicit()) { + Quals == Qualifiers::Const && !Ctor->isExplicit()) { CopyCtor = Ctor; if (Ctor->getTrailingRequiresClause()) FindCopyCtorWithTrailingRequiresClause = true; >From 4a57a647e1067398708cf940d8e7142453ee421c Mon Sep 17 00:00:00 2001 From: Yanzuo Liu <[email protected]> Date: Fri, 1 May 2026 00:29:42 +0800 Subject: [PATCH 08/10] Use `FunctionDecl::isIneligibleOrNotSelected` --- clang/lib/Sema/SemaTemplate.cpp | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 3faefc941ba23..97748e3b2c94e 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -7153,26 +7153,19 @@ static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, return false; } else { - // If there is a copy constructor with const parameter and without explicit, - // it should be selected by overload resolution. - CXXConstructorDecl *CopyCtor = nullptr; - - // If there is trailing requires clause, let overload resolution handle it. - bool FindCopyCtorWithTrailingRequiresClause = false; // A template parameter with this type has been initialized, // so implicitly-declared functions should be declared, // and explicit(bool) should be resolved. - for (CXXConstructorDecl *Ctor : CXXRecord->ctors()) - if (unsigned Quals; Ctor->isCopyConstructor(Quals) && - Quals == Qualifiers::Const && !Ctor->isExplicit()) { - CopyCtor = Ctor; - if (Ctor->getTrailingRequiresClause()) - FindCopyCtorWithTrailingRequiresClause = true; - } - - if (CopyCtor && !FindCopyCtorWithTrailingRequiresClause && - CopyCtor->isTrivial() && !CopyCtor->isDeleted()) + if (llvm::any_of(CXXRecord->ctors(), [](CXXConstructorDecl *Ctor) { + unsigned Quals; + // TODO: After correctly marking deleted methods as ineligible, + // `!Ctor->isDeleted()` is redundant and can be dropped. + // See comment in `SetEligibleMethods` in SemaDecl.cpp. + return Ctor->isCopyConstructor(Quals) && Quals == Qualifiers::Const && + !Ctor->isIneligibleOrNotSelected() && Ctor->isTrivial() && + !Ctor->isDeleted() && !Ctor->isExplicit(); + })) return false; } >From f288d8891f7ae98009ad22b3b225fc65ea5e20c2 Mon Sep 17 00:00:00 2001 From: Yanzuo Liu <[email protected]> Date: Tue, 12 May 2026 13:23:21 +0800 Subject: [PATCH 09/10] Fix release note, document return value, remove redundant condition, and add test. --- clang/docs/ReleaseNotes.rst | 2 +- clang/lib/Sema/SemaTemplate.cpp | 5 ++++- .../test/SemaTemplate/temp_arg_nontype_cxx20.cpp | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 599b17b5ad2ff..e1f403614a630 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -49,7 +49,7 @@ C++ Specific Potentially Breaking Changes - Clang now correctly rejects ``export`` declarations in module implementation partitions. (#GH107602) -- Template arguments which aren't equivalent to their copies are correctly rejected. +- Template arguments which aren't template-argument-equivalent to their copies are correctly rejected. ABI Changes in This Version --------------------------- diff --git a/clang/lib/Sema/SemaTemplate.cpp b/clang/lib/Sema/SemaTemplate.cpp index 97748e3b2c94e..3650dfe966cef 100644 --- a/clang/lib/Sema/SemaTemplate.cpp +++ b/clang/lib/Sema/SemaTemplate.cpp @@ -7132,10 +7132,14 @@ static bool CheckTemplateArgumentPointerToMember( // P2308R1 C++26 [temp.arg.nontype]p4: // ... If, for the initialization from any candidate initializer, +// - the initialization would be ill-formed, or // - ... // - the initialization would cause P to not be // template-argument-equivalent ([temp.type]) to v, // the program is ill-formed. +// +// Returns `false` if they are template-argument-equivalent, `true` if the +// initialization fails or they are not template-argument-equivalent. static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, QualType ParamType, const APValue &Value, @@ -7148,7 +7152,6 @@ static bool CheckTemplateArgumentCopyEquivalence(Sema &S, NamedDecl *Param, !CXXRecord->hasUserDeclaredConstructor()) { if (CXXRecord->hasTrivialCopyConstructor() && - CXXRecord->implicitCopyConstructorHasConstParam() && !CXXRecord->needsOverloadResolutionForCopyConstructor()) return false; diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp index 4af2931f4d9f3..a067474fe64b1 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx20.cpp @@ -226,6 +226,21 @@ template struct T5<{}>; // expected-note@#CannotCopy-T5-S5 {{passing argument to parameter here}} // expected-note@#CannotCopy-T5-S5 {{non-type template argument is required to be copyable}} static_assert(!C<S5<>, T5>); + +struct Base6 { + Base6() = default; + Base6(Base6&) = default; +}; +struct S6 : Base6 {}; // #CannotCopy-S6 +template <S6> // #CannotCopy-T6-S6 +struct T6 {}; +template struct T6<{}>; +// expected-error@-1 {{no matching constructor for initialization of 'S6'}} +// expected-note@#CannotCopy-S6 {{candidate constructor (the implicit copy constructor) not viable: 1st argument ('const S6') would lose const qualifier}} +// expected-note@#CannotCopy-S6 {{candidate constructor (the implicit default constructor) not viable: requires 0 arguments, but 1 was provided}} +// expected-note@#CannotCopy-T6-S6 {{passing argument to parameter here}} +// expected-note@#CannotCopy-T6-S6 {{non-type template argument is required to be copyable}} +static_assert(!C<S6, T6>); } namespace StableAddress { >From 8c0c510771668aa58cce742a7acfd00529c7fe70 Mon Sep 17 00:00:00 2001 From: Yanzuo Liu <[email protected]> Date: Tue, 28 Jul 2026 10:58:48 +0800 Subject: [PATCH 10/10] Revert assertion and add test --- clang/lib/AST/ASTContext.cpp | 2 -- .../SemaTemplate/temp_arg_nontype_cxx2c.cpp | 21 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index e520ef7eec0ce..abf5f8a832043 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -13793,8 +13793,6 @@ ASTContext::getUnnamedGlobalConstantDecl(QualType Ty, TemplateParamObjectDecl * ASTContext::getTemplateParamObjectDecl(QualType T, const APValue &V) const { assert(T->isRecordType() && "template param object of unexpected type"); - assert(!T->isInstantiationDependentType() && - "instantiation-dependent types are not supported"); // C++ [temp.param]p8: // [...] a static storage duration object of type 'const T' [...] diff --git a/clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp b/clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp index c4ac36e263bc8..27f30908e767b 100644 --- a/clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp +++ b/clang/test/SemaTemplate/temp_arg_nontype_cxx2c.cpp @@ -124,6 +124,27 @@ Set<float> sf; } // namespace GH84052 +namespace GH84052_variant { + +struct A { + A() = default; + constexpr A(const A&) {} +}; + +template <class T, GH84052::C<T> auto = A{}> struct Set {}; // #Set1 + +template <class T> void foo() { + Set<T> unrelated; +} + +Set<bool> sb; +Set<float> sf; +// expected-error@-1 {{constraints not satisfied for class template 'Set'}} +// expected-note@#Set1 {{because 'GH84052::C<decltype(GH84052_variant::A{}), float>' evaluated to false}} +// expected-note@#C {{evaluated to false}} + +} // namespace GH84052_variant + namespace error_on_type_instantiation { int f(int) = delete; // expected-note@-1 {{candidate function has been explicitly deleted}} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
