]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: constexpr union placement new [PR121068]
authorJason Merrill <jason@redhat.com>
Wed, 16 Jul 2025 15:52:45 +0000 (11:52 -0400)
committerJason Merrill <jason@redhat.com>
Tue, 22 Jul 2025 21:34:56 +0000 (17:34 -0400)
The note and example in [class.union] p6 think that placement new can be
used to change the active member of a union, but we didn't support that for
array members in constant-evaluation even after implementing P1330 and
P2747.

First I tried to address this by introducing a CLOBBER_BEGIN_OBJECT for the
entire array, but that broke the resolution of LWG3436, which invokes 'new
T[1]' for an array T, and trying to clobber a multidimensional array when
the actual object is single-dimensional breaks.  So I've raised that issue
with the committee.  Until that is resolved, this patch takes a simpler
approach: allow initialization of an element of an array to make the array
the active member of a union.

PR c++/121068

gcc/cp/ChangeLog:

* constexpr.cc (cxx_eval_store_expression): Allow ARRAY_REFs
when activating an array member of a union.

gcc/testsuite/ChangeLog:

* g++.dg/cpp2a/constexpr-union6.C: Expect x5 to work.
* g++.dg/cpp26/constexpr-new4.C: New test.

gcc/cp/constexpr.cc
gcc/testsuite/g++.dg/cpp26/constexpr-new4.C [new file with mode: 0644]
gcc/testsuite/g++.dg/cpp2a/constexpr-union6.C

index ee06858f7153d97335c7abe347d39d3170361a48..1a77954b5acc6b0f9b304cacf88f3382800b0f05 100644 (file)
@@ -7736,13 +7736,24 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
          CONSTRUCTOR_APPEND_ELT (CONSTRUCTOR_ELTS (*valp), first, NULL_TREE);
 
       /* Check for implicit change of active member for a union.  */
+
+      /* LWG3436, CWG2675, c++/121068: The array object model is confused.  For
+        now allow initializing an array element to activate the array.  */
+      auto only_array_refs = [](const releasing_vec &refs)
+      {
+       for (unsigned i = 1; i < refs->length(); i += 3)
+         if (TREE_CODE ((*refs)[i]) != INTEGER_CST)
+           return false;
+       return true;
+      };
+
       if (code == UNION_TYPE
          && (CONSTRUCTOR_NELTS (*valp) == 0
              || CONSTRUCTOR_ELT (*valp, 0)->index != index)
          /* An INIT_EXPR of the last member in an access chain is always OK,
             but still check implicit change of members earlier on; see
             cpp2a/constexpr-union6.C.  */
-         && !(TREE_CODE (t) == INIT_EXPR && refs->is_empty ()))
+         && !(TREE_CODE (t) == INIT_EXPR && only_array_refs (refs)))
        {
          bool has_active_member = CONSTRUCTOR_NELTS (*valp) != 0;
          tree inner = strip_array_types (reftype);
diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-new4.C b/gcc/testsuite/g++.dg/cpp26/constexpr-new4.C
new file mode 100644 (file)
index 0000000..12d8a46
--- /dev/null
@@ -0,0 +1,21 @@
+// PR c++/121068
+// { dg-do compile { target c++26 } }
+
+constexpr void *operator new (__SIZE_TYPE__, void *p) { return p; }
+constexpr void *operator new[] (__SIZE_TYPE__, void *p) { return p; }
+
+consteval int
+foo()
+{
+    using T = int;
+    union { T arr[3]; };
+    new(arr) T[3]; // makes arr active
+    for (int i = 0; i < 3; ++i)
+      arr[i].~T();
+
+    new (arr + 2) T{10}; // A
+
+    return 1;
+};
+
+constexpr int g = foo();
index 00bda531e59605b4aeb00e22960efa2fde46278a..ab8c979dc96c9d440785f4dec39cdbfac6491981 100644 (file)
@@ -45,9 +45,9 @@ constexpr int test5() {
   union {
     int data[1];
   } u;
-  std::construct_at(u.data, 0);  // { dg-message "in .constexpr. expansion" }
+  std::construct_at(u.data, 0);  // { dg-bogus "in .constexpr. expansion" }
   return 0;
 }
-constexpr int x5 = test5();  // { dg-message "in .constexpr. expansion" }
+constexpr int x5 = test5();  // { dg-bogus "in .constexpr. expansion" }
 
 // { dg-error "accessing (uninitialized member|.* member instead of)" "" { target *-*-* } 0 }