[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