c++: C++17 constexpr static data member linkage [PR99901]
authorJason Merrill <jason@redhat.com>
Tue, 6 Apr 2021 05:21:05 +0000 (01:21 -0400)
committerJason Merrill <jason@redhat.com>
Tue, 6 Apr 2021 18:30:02 +0000 (14:30 -0400)
C++17 makes constexpr static data members implicitly inline variables.  In
C++14, a subsequent out-of-class declaration is the definition.  We want to
continue emitting a symbol for such a declaration in C++17 mode, for ABI
compatibility with C++14 code that wants to refer to it.

Normally I'd distinguish in- and out-of-class declarations by looking at
DECL_IN_AGGR_P, but we never set DECL_IN_AGGR_P on inline variables.  I
think that's wrong, but don't want to mess with it so close to release.
Conveniently, we already have a test for in-class declaration earlier in the
function.

gcc/cp/ChangeLog:

PR c++/99901
* decl.c (cp_finish_decl): mark_needed an implicitly inline
static data member with an out-of-class redeclaration.

gcc/testsuite/ChangeLog:

PR c++/99901
* g++.dg/cpp1z/inline-var9.C: New test.

gcc/cp/decl.c
gcc/testsuite/g++.dg/cpp1z/inline-var9.C [new file with mode: 0644]

index 6789aa8..edab147 100644 (file)
@@ -7693,10 +7693,13 @@ cp_finish_decl (tree decl, tree init, bool init_const_expr_p,
   if (asmspec_tree && asmspec_tree != error_mark_node)
     asmspec = TREE_STRING_POINTER (asmspec_tree);
 
-  if (current_class_type
-      && CP_DECL_CONTEXT (decl) == current_class_type
-      && TYPE_BEING_DEFINED (current_class_type)
-      && !CLASSTYPE_TEMPLATE_INSTANTIATION (current_class_type)
+  bool in_class_decl
+    = (current_class_type
+       && CP_DECL_CONTEXT (decl) == current_class_type
+       && TYPE_BEING_DEFINED (current_class_type)
+       && !CLASSTYPE_TEMPLATE_INSTANTIATION (current_class_type));
+
+  if (in_class_decl
       && (DECL_INITIAL (decl) || init))
     DECL_INITIALIZED_IN_CLASS_P (decl) = 1;
 
@@ -8069,6 +8072,13 @@ cp_finish_decl (tree decl, tree init, bool init_const_expr_p,
          if (!flag_weak)
            /* Check again now that we have an initializer.  */
            maybe_commonize_var (decl);
+         /* A class-scope constexpr variable with an out-of-class declaration.
+            C++17 makes them implicitly inline, but still force it out.  */
+         if (DECL_INLINE_VAR_P (decl)
+             && !DECL_VAR_DECLARED_INLINE_P (decl)
+             && !DECL_TEMPLATE_INSTANTIATION (decl)
+             && !in_class_decl)
+           mark_needed (decl);
        }
 
       if (var_definition_p
diff --git a/gcc/testsuite/g++.dg/cpp1z/inline-var9.C b/gcc/testsuite/g++.dg/cpp1z/inline-var9.C
new file mode 100644 (file)
index 0000000..43c9748
--- /dev/null
@@ -0,0 +1,40 @@
+// PR c++/99901
+// { dg-do compile { target c++11 } }
+// { dg-final { scan-assembler-not "_ZN1A1aE" } }
+// { dg-final { scan-assembler-not "_ZN2A21aE" } }
+// { dg-final { scan-assembler-not "_ZN1CIiE1cE" } }
+// { dg-final { scan-assembler "_ZN1B1bE" } }
+// { dg-final { scan-assembler "_ZN2B21bE" } }
+// { dg-final { scan-assembler "_ZN2B31bE" } }
+
+struct A {
+  static const int a = 5;
+};
+
+struct A2 {
+  static constexpr int a = 5;
+};
+
+struct B {
+  static const int b;
+};
+constexpr int B::b = 5;
+
+struct B2 {
+  static const int b = 5;
+};
+constexpr int B2::b;
+
+struct B3 {
+  static constexpr int b = 5;
+};
+const int B3::b;
+
+template <class T>
+struct C {
+  static constexpr int c = 5;
+};
+template <class T>
+constexpr int C<T>::c;
+
+int i = C<int>::c;