[clang] [Clang][Sema] Reject template arguments not equivalent to their copies (part of P2308R1) (PR #193754)
Yanzuo Liu via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 28 06:32:21 PDT 2026
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 <zwuis at outlook.com>
Date: Thu, 23 Apr 2026 21:48:57 +0800
Subject: [PATCH 1/5] 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 <zwuis at outlook.com>
Date: Sun, 26 Apr 2026 12:22:52 +0800
Subject: [PATCH 2/5] 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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -1 {{non-type template argument is not a constant expression}}
+// expected-note at -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 <zwuis at outlook.com>
Date: Mon, 27 Apr 2026 22:56:17 +0800
Subject: [PATCH 3/5] Update clang/lib/Sema/SemaTemplate.cpp
Co-authored-by: Corentin Jabot <corentinjabot at gmail.com>
---
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 <zwuis at outlook.com>
Date: Mon, 27 Apr 2026 23:22:55 +0800
Subject: [PATCH 4/5] 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 at -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 at -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 at -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 at -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 at -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 at -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 at -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 at -1 {{non-type template argument is not a constant expression}}
-// expected-note at -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 at -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 <zwuis at outlook.com>
Date: Tue, 28 Apr 2026 21:26:01 +0800
Subject: [PATCH 5/5] 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,
More information about the cfe-commits
mailing list