[clang] [clang] Fix handling of constexpr static data member template. (PR #220469)
Eli Friedman via cfe-commits
cfe-commits at lists.llvm.org
Mon Sep 14 16:52:31 PDT 2026
https://github.com/efriedma-quic updated https://github.com/llvm/llvm-project/pull/220469
>From 6b14eeaa8b58b0b3290bb2f6a4f52cb1fcdb595d Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Tue, 1 Sep 2026 20:20:39 -0700
Subject: [PATCH 1/3] [clang] Fix handling of constexpr static data member
template.
[temp.expl.spec] says "An explicit specialization of a static data
member of a template or an explicit specialization of a static data
member template is a definition if the declaration includes an
initializer." The code did not match this rule: it carved out an
exception for constexpr variables.
I'm not confident we have enough test coverage here to tell if this
breaks anything else.
---
clang/lib/AST/Decl.cpp | 2 --
.../CodeGenCXX/cxx1z-inline-variables.cpp | 16 ++++++++++
clang/test/SemaCXX/dllimport.cpp | 31 +++++++------------
.../test/SemaTemplate/class-template-spec.cpp | 26 ++++++++++++++--
4 files changed, 51 insertions(+), 24 deletions(-)
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index c9524dc82588a..718883cce10aa 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2258,8 +2258,6 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const {
// a static data member template outside the containing class?
if (isStaticDataMember()) {
if (isOutOfLine() &&
- !(getCanonicalDecl()->isInline() &&
- getCanonicalDecl()->isConstexpr()) &&
(hasInit() ||
// If the first declaration is out-of-line, this may be an
// instantiation of an out-of-line partial specialization of a variable
diff --git a/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp b/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp
index 9b1a6e4647e85..95011b6edf240 100644
--- a/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp
+++ b/clang/test/CodeGenCXX/cxx1z-inline-variables.cpp
@@ -101,6 +101,22 @@ constexpr int Y<int>::a;
const int &yib = Y<int>::b;
// CHECK-NOT: @_ZN1YIiE1cE
+namespace PR219796 {
+template<typename> struct A;
+template<>
+struct A<void> {
+ template<int> static int value;
+};
+
+// CHECK: _ZN8PR2197961AIvE5valueILi101EEE = weak_odr constant i32 3
+template<> constexpr int A<void>::value<101> = 3;
+
+const int& f()
+{
+ return A<void>::template value<101>;
+}
+}
+
// CHECK-LABEL: define {{.*}}global_var_init
// CHECK: call noundef i32 @_Z1fv
diff --git a/clang/test/SemaCXX/dllimport.cpp b/clang/test/SemaCXX/dllimport.cpp
index cecabe98cfb5c..c31ecbcb9c8de 100644
--- a/clang/test/SemaCXX/dllimport.cpp
+++ b/clang/test/SemaCXX/dllimport.cpp
@@ -595,9 +595,7 @@ struct ImportMembers {
__declspec(dllimport) static const int StaticConstFieldEqualInit = 1;
__declspec(dllimport) static const int StaticConstFieldBraceInit{1};
__declspec(dllimport) constexpr static int ConstexprField = 1;
-#if __cplusplus < 201703L && !defined(MS)
- // expected-note at +2{{attribute is here}}
-#endif
+ // expected-note at +1{{attribute is here}}
__declspec(dllimport) constexpr static int ConstexprFieldDef = 1;
};
@@ -643,9 +641,7 @@ inline void ImportMembers::staticInlineDef() {}
int ImportMembers::StaticFieldDef; // expected-error{{definition of dllimport static field not allowed}}
const int ImportMembers::StaticConstFieldDef = 1; // expected-error{{definition of dllimport static field not allowed}}
-#if __cplusplus < 201703L && !defined(MS)
-// expected-error at +2{{definition of dllimport static field not allowed}}
-#endif
+// expected-error at +1{{definition of dllimport static field not allowed}}
constexpr int ImportMembers::ConstexprFieldDef;
@@ -682,10 +678,8 @@ __declspec(dllimport) void ImportMemberDefs::staticInlineDecl() {}
__declspec(dllimport) int ImportMemberDefs::StaticField; // expected-error{{definition of dllimport static field not allowed}} expected-note{{attribute is here}}
__declspec(dllimport) const int ImportMemberDefs::StaticConstField = 1; // expected-error{{definition of dllimport static field not allowed}} expected-note{{attribute is here}}
-#if __cplusplus < 201703L && !defined(MS)
-// expected-error at +3{{definition of dllimport static field not allowed}}
-// expected-note at +2{{attribute is here}}
-#endif
+// expected-error at +2{{definition of dllimport static field not allowed}}
+// expected-note at +1{{attribute is here}}
__declspec(dllimport) constexpr int ImportMemberDefs::ConstexprField;
@@ -900,6 +894,9 @@ struct ImportMemberTmpl {
template<typename T> __declspec(dllimport) static const int StaticConstFieldEqualInit = 1;
template<typename T> __declspec(dllimport) static const int StaticConstFieldBraceInit{1};
template<typename T> __declspec(dllimport) constexpr static int ConstexprField = 1;
+#ifdef MS
+ // expected-note at +2{{attribute is here}}
+#endif
template<typename T> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1;
#endif // __has_feature(cxx_variable_templates)
};
@@ -926,7 +923,7 @@ template<typename T> inline void ImportMemberTmpl::staticInlineDef() {} // expec
template<typename T> int ImportMemberTmpl::StaticFieldDef; // expected-error{{definition of dllimport static field not allowed}}
template<typename T> const int ImportMemberTmpl::StaticConstFieldDef = 1; // expected-error{{definition of dllimport static field not allowed}}
#ifdef MS
-template<typename T> constexpr int ImportMemberTmpl::ConstexprFieldDef;
+template<typename T> constexpr int ImportMemberTmpl::ConstexprFieldDef; // expected-error{{definition of dllimport static field not allowed}}
#endif
#endif // __has_feature(cxx_variable_templates)
@@ -1192,9 +1189,7 @@ struct ImportClassTmplMembers {
__declspec(dllimport) static const int StaticConstFieldEqualInit = 1;
__declspec(dllimport) static const int StaticConstFieldBraceInit{1};
__declspec(dllimport) constexpr static int ConstexprField = 1;
-#if __cplusplus < 201703L && !defined(MS)
- // expected-note at +2{{attribute is here}}
-#endif
+ // expected-note at +1{{attribute is here}}
__declspec(dllimport) constexpr static int ConstexprFieldDef = 1;
};
@@ -1239,9 +1234,7 @@ template<typename T> void ImportClassTmplMembers<T>::staticInlineDecl() {
template<typename T> int ImportClassTmplMembers<T>::StaticFieldDef; // expected-warning{{definition of dllimport static field}}
template<typename T> const int ImportClassTmplMembers<T>::StaticConstFieldDef = 1; // expected-warning{{definition of dllimport static field}}
-#if __cplusplus < 201703L && !defined(MS)
-// expected-warning at +2{{definition of dllimport static field}}
-#endif
+// expected-warning at +1{{definition of dllimport static field}}
template<typename T> constexpr int ImportClassTmplMembers<T>::ConstexprFieldDef;
@@ -1380,7 +1373,7 @@ struct ImportClsTmplMemTmpl {
template<typename U> __declspec(dllimport) static const int StaticConstFieldBraceInit{1};
template<typename U> __declspec(dllimport) constexpr static int ConstexprField = 1;
#ifdef MS
- template<typename U> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1;
+ template<typename U> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; // expected-note{{attribute is here}}
#endif
#endif // __has_feature(cxx_variable_templates)
};
@@ -1407,7 +1400,7 @@ template<typename T> template<typename U> inline void ImportClsTmplMemTmpl<T>::s
template<typename T> template<typename U> int ImportClsTmplMemTmpl<T>::StaticFieldDef; // expected-warning{{definition of dllimport static field}}
template<typename T> template<typename U> const int ImportClsTmplMemTmpl<T>::StaticConstFieldDef = 1; // expected-warning{{definition of dllimport static field}}
#ifdef MS
-template<typename T> template<typename U> constexpr int ImportClsTmplMemTmpl<T>::ConstexprFieldDef;
+template<typename T> template<typename U> constexpr int ImportClsTmplMemTmpl<T>::ConstexprFieldDef; // expected-warning {{definition of dllimport static field}}
#endif
#endif // __has_feature(cxx_variable_templates)
diff --git a/clang/test/SemaTemplate/class-template-spec.cpp b/clang/test/SemaTemplate/class-template-spec.cpp
index e60763feb2e1f..4a2f7c3efdb89 100644
--- a/clang/test/SemaTemplate/class-template-spec.cpp
+++ b/clang/test/SemaTemplate/class-template-spec.cpp
@@ -1,8 +1,8 @@
-// RUN: %clang_cc1 -fsyntax-only -verify %s
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx17orlater -std=c++17 %s
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++98 %s
// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 %s
// RUN: %clang_cc1 -fsyntax-only -triple x86_64-linux-gnu -verify=expected,cxx14 -std=c++14 %s
-// RUN: %clang_cc1 -fsyntax-only -verify -std=c++26 %s
+// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx17orlater -std=c++26 %s
template<typename T, typename U = int> struct A; // expected-note {{template is declared here}} \
// expected-note{{explicitly specialized}}
@@ -259,8 +259,9 @@ namespace VarTemplateNoMember {
template<> template<typename U> constexpr int S<long>::foo;
// In C++14, these are definitions, not declarations, so they get a
// redefinition error.
- // cxx14-error at +2{{redefinition of 'foo'}}
+ // cxx14-error at +3{{redefinition of 'foo'}}
// cxx14-note at -4{{previous definition is here}}
+ // cxx17orlater-error at +1 {{must be initialized by a constant expression}}
template<> template<typename U> constexpr int S<long>::foo;
// cxx14-error at +2{{redefinition of 'foo'}}
// cxx14-note at -2{{previous definition is here}}
@@ -273,3 +274,22 @@ namespace VarTemplateNoMember {
template<> template<typename U> constexpr int S<long>::foo;
} // namespace VarTemplateNoMember
#endif
+
+#if __cplusplus >= 201703L
+namespace ConstexprVarTemplateRedeclared {
+template<typename> struct A;
+struct SS {};
+template<>
+struct A<void> {
+ template<int> static inline SS value = {};
+};
+
+template<> inline constexpr SS A<void>::value<101> = {};
+template<> inline constexpr SS A<void>::value<101>;
+
+const SS& f()
+{
+ return A<void>::value<101>;
+}
+}
+#endif
>From 2911115ab26d0cf16366b2e72933ae2299fe386f Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Mon, 14 Sep 2026 16:43:52 -0700
Subject: [PATCH 2/3] Add back handling for C++17 out-of-line declarations.
---
clang/lib/AST/Decl.cpp | 3 +++
clang/test/SemaCXX/dllimport.cpp | 31 +++++++++++++++++++------------
2 files changed, 22 insertions(+), 12 deletions(-)
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 718883cce10aa..3eca92255acfc 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2258,6 +2258,9 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const {
// a static data member template outside the containing class?
if (isStaticDataMember()) {
if (isOutOfLine() &&
+ !(getCanonicalDecl()->isInline() &&
+ getCanonicalDecl()->isConstexpr() &&
+ !getCanonicalDecl()->isOutOfLine()) &&
(hasInit() ||
// If the first declaration is out-of-line, this may be an
// instantiation of an out-of-line partial specialization of a variable
diff --git a/clang/test/SemaCXX/dllimport.cpp b/clang/test/SemaCXX/dllimport.cpp
index c31ecbcb9c8de..cecabe98cfb5c 100644
--- a/clang/test/SemaCXX/dllimport.cpp
+++ b/clang/test/SemaCXX/dllimport.cpp
@@ -595,7 +595,9 @@ struct ImportMembers {
__declspec(dllimport) static const int StaticConstFieldEqualInit = 1;
__declspec(dllimport) static const int StaticConstFieldBraceInit{1};
__declspec(dllimport) constexpr static int ConstexprField = 1;
- // expected-note at +1{{attribute is here}}
+#if __cplusplus < 201703L && !defined(MS)
+ // expected-note at +2{{attribute is here}}
+#endif
__declspec(dllimport) constexpr static int ConstexprFieldDef = 1;
};
@@ -641,7 +643,9 @@ inline void ImportMembers::staticInlineDef() {}
int ImportMembers::StaticFieldDef; // expected-error{{definition of dllimport static field not allowed}}
const int ImportMembers::StaticConstFieldDef = 1; // expected-error{{definition of dllimport static field not allowed}}
-// expected-error at +1{{definition of dllimport static field not allowed}}
+#if __cplusplus < 201703L && !defined(MS)
+// expected-error at +2{{definition of dllimport static field not allowed}}
+#endif
constexpr int ImportMembers::ConstexprFieldDef;
@@ -678,8 +682,10 @@ __declspec(dllimport) void ImportMemberDefs::staticInlineDecl() {}
__declspec(dllimport) int ImportMemberDefs::StaticField; // expected-error{{definition of dllimport static field not allowed}} expected-note{{attribute is here}}
__declspec(dllimport) const int ImportMemberDefs::StaticConstField = 1; // expected-error{{definition of dllimport static field not allowed}} expected-note{{attribute is here}}
-// expected-error at +2{{definition of dllimport static field not allowed}}
-// expected-note at +1{{attribute is here}}
+#if __cplusplus < 201703L && !defined(MS)
+// expected-error at +3{{definition of dllimport static field not allowed}}
+// expected-note at +2{{attribute is here}}
+#endif
__declspec(dllimport) constexpr int ImportMemberDefs::ConstexprField;
@@ -894,9 +900,6 @@ struct ImportMemberTmpl {
template<typename T> __declspec(dllimport) static const int StaticConstFieldEqualInit = 1;
template<typename T> __declspec(dllimport) static const int StaticConstFieldBraceInit{1};
template<typename T> __declspec(dllimport) constexpr static int ConstexprField = 1;
-#ifdef MS
- // expected-note at +2{{attribute is here}}
-#endif
template<typename T> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1;
#endif // __has_feature(cxx_variable_templates)
};
@@ -923,7 +926,7 @@ template<typename T> inline void ImportMemberTmpl::staticInlineDef() {} // expec
template<typename T> int ImportMemberTmpl::StaticFieldDef; // expected-error{{definition of dllimport static field not allowed}}
template<typename T> const int ImportMemberTmpl::StaticConstFieldDef = 1; // expected-error{{definition of dllimport static field not allowed}}
#ifdef MS
-template<typename T> constexpr int ImportMemberTmpl::ConstexprFieldDef; // expected-error{{definition of dllimport static field not allowed}}
+template<typename T> constexpr int ImportMemberTmpl::ConstexprFieldDef;
#endif
#endif // __has_feature(cxx_variable_templates)
@@ -1189,7 +1192,9 @@ struct ImportClassTmplMembers {
__declspec(dllimport) static const int StaticConstFieldEqualInit = 1;
__declspec(dllimport) static const int StaticConstFieldBraceInit{1};
__declspec(dllimport) constexpr static int ConstexprField = 1;
- // expected-note at +1{{attribute is here}}
+#if __cplusplus < 201703L && !defined(MS)
+ // expected-note at +2{{attribute is here}}
+#endif
__declspec(dllimport) constexpr static int ConstexprFieldDef = 1;
};
@@ -1234,7 +1239,9 @@ template<typename T> void ImportClassTmplMembers<T>::staticInlineDecl() {
template<typename T> int ImportClassTmplMembers<T>::StaticFieldDef; // expected-warning{{definition of dllimport static field}}
template<typename T> const int ImportClassTmplMembers<T>::StaticConstFieldDef = 1; // expected-warning{{definition of dllimport static field}}
-// expected-warning at +1{{definition of dllimport static field}}
+#if __cplusplus < 201703L && !defined(MS)
+// expected-warning at +2{{definition of dllimport static field}}
+#endif
template<typename T> constexpr int ImportClassTmplMembers<T>::ConstexprFieldDef;
@@ -1373,7 +1380,7 @@ struct ImportClsTmplMemTmpl {
template<typename U> __declspec(dllimport) static const int StaticConstFieldBraceInit{1};
template<typename U> __declspec(dllimport) constexpr static int ConstexprField = 1;
#ifdef MS
- template<typename U> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1; // expected-note{{attribute is here}}
+ template<typename U> __declspec(dllimport) constexpr static int ConstexprFieldDef = 1;
#endif
#endif // __has_feature(cxx_variable_templates)
};
@@ -1400,7 +1407,7 @@ template<typename T> template<typename U> inline void ImportClsTmplMemTmpl<T>::s
template<typename T> template<typename U> int ImportClsTmplMemTmpl<T>::StaticFieldDef; // expected-warning{{definition of dllimport static field}}
template<typename T> template<typename U> const int ImportClsTmplMemTmpl<T>::StaticConstFieldDef = 1; // expected-warning{{definition of dllimport static field}}
#ifdef MS
-template<typename T> template<typename U> constexpr int ImportClsTmplMemTmpl<T>::ConstexprFieldDef; // expected-warning {{definition of dllimport static field}}
+template<typename T> template<typename U> constexpr int ImportClsTmplMemTmpl<T>::ConstexprFieldDef;
#endif
#endif // __has_feature(cxx_variable_templates)
>From 4663abda1c09b1e6ad6d244e5931b26b22a410d4 Mon Sep 17 00:00:00 2001
From: Eli Friedman <efriedma at qti.qualcomm.com>
Date: Mon, 14 Sep 2026 16:52:05 -0700
Subject: [PATCH 3/3] Fixups.
---
clang/docs/ReleaseNotes.md | 3 +++
clang/lib/AST/Decl.cpp | 3 +--
2 files changed, 4 insertions(+), 2 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 043a0ddae2a6c..93742675b4478 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -676,6 +676,9 @@ features cannot lower the translation-unit ABI level;
class with an invalid non-static data member, such as one qualified with an
address space. (#GH194605)
+- Fixed an issue where an explicit specialization of a constexpr variable would
+ result in a link error. (#GH219796)
+
#### Bug Fixes to AST Handling
- Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index a0670a3c8280a..34f5d0abe5f74 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -2258,8 +2258,7 @@ VarDecl::isThisDeclarationADefinition(ASTContext &C) const {
// a static data member template outside the containing class?
if (isStaticDataMember()) {
if (isOutOfLine() &&
- !(getCanonicalDecl()->isInline() &&
- getCanonicalDecl()->isConstexpr() &&
+ !(getCanonicalDecl()->isInline() && getCanonicalDecl()->isConstexpr() &&
!getCanonicalDecl()->isOutOfLine()) &&
(hasInit() ||
// If the first declaration is out-of-line, this may be an
More information about the cfe-commits
mailing list