c++: constexpr union placement new [PR121068]

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.
This commit is contained in:
Jason Merrill
2025-07-16 11:52:45 -04:00
parent 9feecd152f
commit fdbc5ff61b
3 changed files with 35 additions and 3 deletions

View 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);

View File

@@ -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();

View 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 }