aboutsummaryrefslogtreecommitdiff
path: root/gcc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc')
-rw-r--r--gcc/cp/call.cc11
-rw-r--r--gcc/cp/class.cc8
-rw-r--r--gcc/cp/constexpr.cc166
-rw-r--r--gcc/cp/cp-tree.h1
-rw-r--r--gcc/testsuite/g++.dg/cpp1y/constexpr-89336-3.C2
-rw-r--r--gcc/testsuite/g++.dg/cpp1y/constexpr-union6.C13
-rw-r--r--gcc/testsuite/g++.dg/cpp1y/constexpr-union7.C18
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/constexpr-union2.C30
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/constexpr-union3.C45
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/constexpr-union4.C29
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/constexpr-union5.C80
-rw-r--r--gcc/testsuite/g++.dg/cpp2a/constexpr-union6.C53
12 files changed, 403 insertions, 53 deletions
diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc
index e8dafbd..c1fb880 100644
--- a/gcc/cp/call.cc
+++ b/gcc/cp/call.cc
@@ -10330,10 +10330,7 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
&& DECL_OVERLOADED_OPERATOR_IS (fn, NOP_EXPR)
&& trivial_fn_p (fn))
{
- /* Don't use cp_build_fold_indirect_ref, op= returns an lvalue even if
- the object argument isn't one. */
- tree to = cp_build_indirect_ref (input_location, argarray[0],
- RO_ARROW, complain);
+ tree to = cp_build_fold_indirect_ref (argarray[0]);
tree type = TREE_TYPE (to);
tree as_base = CLASSTYPE_AS_BASE (type);
tree arg = argarray[1];
@@ -10341,7 +10338,11 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain)
if (is_really_empty_class (type, /*ignore_vptr*/true))
{
- /* Avoid copying empty classes. */
+ /* Avoid copying empty classes, but ensure op= returns an lvalue even
+ if the object argument isn't one. This isn't needed in other cases
+ since MODIFY_EXPR is always considered an lvalue. */
+ to = cp_build_addr_expr (to, tf_none);
+ to = cp_build_indirect_ref (input_location, to, RO_ARROW, complain);
val = build2 (COMPOUND_EXPR, type, arg, to);
suppress_warning (val, OPT_Wunused);
}
diff --git a/gcc/cp/class.cc b/gcc/cp/class.cc
index b71333a..e31aeb8 100644
--- a/gcc/cp/class.cc
+++ b/gcc/cp/class.cc
@@ -5688,6 +5688,14 @@ type_has_virtual_destructor (tree type)
return (dtor && DECL_VIRTUAL_P (dtor));
}
+/* True iff class TYPE has a non-deleted trivial default
+ constructor. */
+
+bool type_has_non_deleted_trivial_default_ctor (tree type)
+{
+ return TYPE_HAS_TRIVIAL_DFLT (type) && locate_ctor (type);
+}
+
/* Returns true iff T, a class, has a move-assignment or
move-constructor. Does not lazily declare either.
If USER_P is false, any move function will do. If it is true, the
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 1d59a2f..6e84c2d 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -3190,40 +3190,34 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
cxx_set_object_constness (ctx, new_obj, /*readonly_p=*/false,
non_constant_p, overflow_p);
+ /* If this is a constructor, we are beginning the lifetime of the
+ object we are initializing. */
+ if (new_obj
+ && DECL_CONSTRUCTOR_P (fun)
+ && TREE_CODE (new_obj) == COMPONENT_REF
+ && TREE_CODE (TREE_TYPE (TREE_OPERAND (new_obj, 0))) == UNION_TYPE)
+ {
+ tree activate = build2 (INIT_EXPR, TREE_TYPE (new_obj),
+ new_obj,
+ build_constructor (TREE_TYPE (new_obj),
+ NULL));
+ cxx_eval_constant_expression (ctx, activate,
+ lval, non_constant_p, overflow_p);
+ ggc_free (activate);
+ }
+
tree jump_target = NULL_TREE;
cxx_eval_constant_expression (&call_ctx, body,
vc_discard, non_constant_p, overflow_p,
&jump_target);
if (DECL_CONSTRUCTOR_P (fun))
- {
- /* This can be null for a subobject constructor call, in
- which case what we care about is the initialization
- side-effects rather than the value. We could get at the
- value by evaluating *this, but we don't bother; there's
- no need to put such a call in the hash table. */
- result = lval ? ctx->object : ctx->ctor;
-
- /* If we've just evaluated a subobject constructor call for an
- empty union member, it might not have produced a side effect
- that actually activated the union member. So produce such a
- side effect now to ensure the union appears initialized. */
- if (!result && new_obj
- && TREE_CODE (new_obj) == COMPONENT_REF
- && TREE_CODE (TREE_TYPE
- (TREE_OPERAND (new_obj, 0))) == UNION_TYPE
- && is_really_empty_class (TREE_TYPE (new_obj),
- /*ignore_vptr*/false))
- {
- tree activate = build2 (MODIFY_EXPR, TREE_TYPE (new_obj),
- new_obj,
- build_constructor (TREE_TYPE (new_obj),
- NULL));
- cxx_eval_constant_expression (ctx, activate, lval,
- non_constant_p, overflow_p);
- ggc_free (activate);
- }
- }
+ /* This can be null for a subobject constructor call, in
+ which case what we care about is the initialization
+ side-effects rather than the value. We could get at the
+ value by evaluating *this, but we don't bother; there's
+ no need to put such a call in the hash table. */
+ result = lval ? ctx->object : ctx->ctor;
else if (VOID_TYPE_P (TREE_TYPE (res)))
result = void_node;
else
@@ -4523,6 +4517,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
*non_constant_p = true;
return t;
}
+
bool pmf = TYPE_PTRMEMFUNC_P (TREE_TYPE (whole));
FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (whole), i, field, value)
{
@@ -4541,21 +4536,36 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
break;
}
}
- if (TREE_CODE (TREE_TYPE (whole)) == UNION_TYPE
- && CONSTRUCTOR_NELTS (whole) > 0)
+ if (TREE_CODE (TREE_TYPE (whole)) == UNION_TYPE)
{
- /* DR 1188 says we don't have to deal with this. */
- if (!ctx->quiet)
+ if (CONSTRUCTOR_NELTS (whole) > 0)
{
- constructor_elt *cep = CONSTRUCTOR_ELT (whole, 0);
- if (cep->value == NULL_TREE)
- error ("accessing uninitialized member %qD", part);
- else
- error ("accessing %qD member instead of initialized %qD member in "
- "constant expression", part, cep->index);
+ /* DR 1188 says we don't have to deal with this. */
+ if (!ctx->quiet)
+ {
+ constructor_elt *cep = CONSTRUCTOR_ELT (whole, 0);
+ if (cep->value == NULL_TREE)
+ error ("accessing uninitialized member %qD", part);
+ else
+ error ("accessing %qD member instead of initialized %qD member "
+ "in constant expression", part, cep->index);
+ }
+ *non_constant_p = true;
+ return t;
+ }
+ else if (!CONSTRUCTOR_NO_CLEARING (whole))
+ {
+ /* Value-initialized union, check if looking at the first member. */
+ tree first = next_aggregate_field (TYPE_FIELDS (TREE_TYPE (whole)));
+ if (first != part)
+ {
+ if (!ctx->quiet)
+ error ("accessing %qD member instead of initialized %qD "
+ "member in constant expression", part, first);
+ *non_constant_p = true;
+ return t;
+ }
}
- *non_constant_p = true;
- return t;
}
/* We only create a CONSTRUCTOR for a subobject when we modify it, so empty
@@ -6172,6 +6182,14 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
mutable_p)
&& const_object_being_modified == NULL_TREE)
const_object_being_modified = probe;
+
+ /* Track named member accesses for unions to validate modifications
+ that change active member. */
+ if (!evaluated && TREE_CODE (probe) == COMPONENT_REF)
+ vec_safe_push (refs, probe);
+ else
+ vec_safe_push (refs, NULL_TREE);
+
vec_safe_push (refs, elt);
vec_safe_push (refs, TREE_TYPE (probe));
probe = ob;
@@ -6180,6 +6198,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
case REALPART_EXPR:
gcc_assert (probe == target);
+ vec_safe_push (refs, NULL_TREE);
vec_safe_push (refs, probe);
vec_safe_push (refs, TREE_TYPE (probe));
probe = TREE_OPERAND (probe, 0);
@@ -6187,6 +6206,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
case IMAGPART_EXPR:
gcc_assert (probe == target);
+ vec_safe_push (refs, NULL_TREE);
vec_safe_push (refs, probe);
vec_safe_push (refs, TREE_TYPE (probe));
probe = TREE_OPERAND (probe, 0);
@@ -6275,6 +6295,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
enum tree_code code = TREE_CODE (type);
tree reftype = refs->pop();
tree index = refs->pop();
+ bool is_access_expr = refs->pop() != NULL_TREE;
if (code == COMPLEX_TYPE)
{
@@ -6313,23 +6334,73 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
break;
}
- type = reftype;
+ /* If a union is zero-initialized, its first non-static named data member
+ is zero-initialized (and therefore active). */
+ if (code == UNION_TYPE
+ && !no_zero_init
+ && CONSTRUCTOR_NELTS (*valp) == 0)
+ if (tree first = next_aggregate_field (TYPE_FIELDS (type)))
+ CONSTRUCTOR_APPEND_ELT (CONSTRUCTOR_ELTS (*valp), first, NULL_TREE);
- if (code == UNION_TYPE && CONSTRUCTOR_NELTS (*valp)
- && CONSTRUCTOR_ELT (*valp, 0)->index != index)
+ /* Check for implicit change of active member for a union. */
+ 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 ()))
{
- if (cxx_dialect < cxx20)
+ bool has_active_member = CONSTRUCTOR_NELTS (*valp) != 0;
+ tree inner = strip_array_types (reftype);
+
+ if (has_active_member && cxx_dialect < cxx20)
{
if (!ctx->quiet)
error_at (cp_expr_loc_or_input_loc (t),
"change of the active member of a union "
- "from %qD to %qD",
+ "from %qD to %qD is not a constant expression "
+ "before C++20",
CONSTRUCTOR_ELT (*valp, 0)->index,
index);
*non_constant_p = true;
}
- else if (TREE_CODE (t) == MODIFY_EXPR
- && CONSTRUCTOR_NO_CLEARING (*valp))
+ else if (!is_access_expr
+ || (TREE_CODE (t) == MODIFY_EXPR
+ && CLASS_TYPE_P (inner)
+ && !type_has_non_deleted_trivial_default_ctor (inner)))
+ {
+ /* Diagnose changing active union member after initialization
+ without a valid member access expression, as described in
+ [class.union.general] p5. */
+ if (!ctx->quiet)
+ {
+ auto_diagnostic_group d;
+ if (has_active_member)
+ error_at (cp_expr_loc_or_input_loc (t),
+ "accessing %qD member instead of initialized "
+ "%qD member in constant expression",
+ index, CONSTRUCTOR_ELT (*valp, 0)->index);
+ else
+ error_at (cp_expr_loc_or_input_loc (t),
+ "accessing uninitialized member %qD",
+ index);
+ if (is_access_expr)
+ inform (DECL_SOURCE_LOCATION (index),
+ "%qD does not implicitly begin its lifetime "
+ "because %qT does not have a non-deleted "
+ "trivial default constructor, use "
+ "%<std::construct_at%> instead",
+ index, inner);
+ else
+ inform (DECL_SOURCE_LOCATION (index),
+ "initializing %qD requires a member access "
+ "expression as the left operand of the assignment",
+ index);
+ }
+ *non_constant_p = true;
+ }
+ else if (has_active_member && CONSTRUCTOR_NO_CLEARING (*valp))
{
/* Diagnose changing the active union member while the union
is in the process of being initialized. */
@@ -6355,6 +6426,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
activated_union_member_p = true;
valp = &cep->value;
+ type = reftype;
}
/* For initialization of an empty base, the original target will be
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 1d7df62..8c75d03 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6817,6 +6817,7 @@ extern bool trivial_default_constructor_is_constexpr (tree);
extern bool type_has_constexpr_default_constructor (tree);
extern bool type_has_constexpr_destructor (tree);
extern bool type_has_virtual_destructor (tree);
+extern bool type_has_non_deleted_trivial_default_ctor (tree);
extern bool classtype_has_move_assign_or_move_ctor_p (tree, bool user_declared);
extern bool classtype_has_non_deleted_move_ctor (tree);
extern tree classtype_has_depr_implicit_copy (tree);
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-89336-3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-89336-3.C
index 9d370dd..6a60966 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-89336-3.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-89336-3.C
@@ -18,7 +18,7 @@ constexpr int
bar ()
{
union U { int a[5]; long b; };
- union V { union U u; short v; };
+ union V { short v; union U u; };
V w {};
w.v = 5;
w.u.a[3] = w.u.a[1] = w.v; // { dg-error "change of the active member of a union from" "" { target c++17_down } }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-union6.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-union6.C
new file mode 100644
index 0000000..ff7ebf1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-union6.C
@@ -0,0 +1,13 @@
+// { dg-do compile { target c++14 } }
+
+union U {
+ int a;
+ float b;
+};
+
+constexpr bool foo() {
+ U u {};
+ u.b = 1.0f; // { dg-error "change of the active member" "" { target c++17_down } }
+ return u.b == 1.0f;
+}
+constexpr bool x = foo();
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-union7.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-union7.C
new file mode 100644
index 0000000..6fc41f9
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-union7.C
@@ -0,0 +1,18 @@
+// { dg-do compile { target c++14 } }
+
+// this type is not value-initialisable
+struct S { const int a; int b; };
+
+union U1 { int k; S s; };
+constexpr int test1() {
+ U1 u {};
+ return u.s.b; // { dg-error "accessing .U1::s. member instead of initialized .U1::k. member" }
+}
+constexpr int x = test1();
+
+union U2 { int :0; static int s; void foo(); int k; };
+constexpr int test2() {
+ U2 u {}; // should skip zero-width bitfields, static members, and functions
+ return u.k;
+}
+static_assert(test2() == 0, "");
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-union2.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-union2.C
new file mode 100644
index 0000000..1712395
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-union2.C
@@ -0,0 +1,30 @@
+// PR c++/101631
+// { dg-do compile { target c++20 } }
+
+struct sso {
+ union {
+ int buf[10];
+ int* alloc;
+ };
+};
+
+constexpr bool direct() {
+ sso val;
+ val.alloc = nullptr;
+ val.buf[5] = 42;
+ return true;
+}
+constexpr bool ok = direct();
+
+
+constexpr void perform_assignment(int& left, int right) noexcept {
+ left = right; // { dg-error "accessing .+ member instead of initialized" }
+}
+
+constexpr bool indirect() {
+ sso val;
+ val.alloc = nullptr;
+ perform_assignment(val.buf[5], 42); // { dg-message "in .constexpr. expansion" }
+ return true;
+}
+constexpr bool err = indirect(); // { dg-message "in .constexpr. expansion" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-union3.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-union3.C
new file mode 100644
index 0000000..6d30bb2
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-union3.C
@@ -0,0 +1,45 @@
+// { dg-do compile { target c++20 } }
+
+struct S
+{
+ union {
+ char buf[8];
+ char* ptr;
+ };
+ unsigned len;
+
+ constexpr S(const char* s, unsigned n)
+ {
+ char* p;
+ if (n > 7)
+ p = ptr = new char[n+1];
+ else
+ p = buf;
+ for (len = 0; len < n; ++len)
+ p[len] = s[len]; // { dg-error "accessing uninitialized member" }
+ p[len] = '\0';
+ }
+
+ constexpr ~S()
+ {
+ if (len > 7)
+ delete[] ptr;
+ }
+};
+
+constexpr bool test1()
+{
+ S s("test", 4); // { dg-message "in .constexpr. expansion" }
+ return true;
+}
+
+constexpr bool a = test1(); // { dg-message "in .constexpr. expansion" }
+
+
+constexpr bool test2()
+{
+ S s("hello world", 11);
+ return true;
+}
+
+constexpr bool b = test2();
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-union4.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-union4.C
new file mode 100644
index 0000000..429ab20
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-union4.C
@@ -0,0 +1,29 @@
+// { dg-do compile { target c++20 } }
+
+// from [class.union.general] p5
+
+union A { int x; int y[4]; };
+struct B { A a; };
+union C { B b; int k; };
+constexpr int f() {
+ C c; // does not start lifetime of any union member
+ c.b.a.y[3] = 4; // OK, S(c.b.a.y[3]) contains c.b and c.b.a.y;
+ // creates objects to hold union members c.b and c.b.a.y
+ return c.b.a.y[3]; // OK, c.b.a.y refers to newly created object (see [basic.life])
+}
+constexpr int a = f();
+
+struct X { const int a; int b; };
+union Y { X x; int k; };// { dg-message "does not implicitly begin its lifetime" }
+constexpr int g() {
+ Y y = { { 1, 2 } }; // OK, y.x is active union member ([class.mem])
+ int n = y.x.a;
+ y.k = 4; // OK, ends lifetime of y.x, y.k is active member of union
+
+ y.x.b = n; // { dg-error "accessing .* member instead of initialized .* member" }
+ // undefined behavior: y.x.b modified outside its lifetime,
+ // S(y.x.b) is empty because X's default constructor is deleted,
+ // so union member y.x's lifetime does not implicitly start
+ return 0;
+}
+constexpr int b = g(); // { dg-message "in .constexpr. expansion" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-union5.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-union5.C
new file mode 100644
index 0000000..7e42522
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-union5.C
@@ -0,0 +1,80 @@
+// { dg-do compile { target c++20 } }
+
+union U { int a; int b; int c[2]; };
+
+constexpr int test1() {
+ U u;
+ u.a = 10;
+ *&u.b = 20; // { dg-error "accessing" }
+ return u.b;
+}
+constexpr int x1 = test1(); // { dg-message "in .constexpr. expansion" }
+
+constexpr int test2() {
+ U u;
+ u.a = 10;
+ (0, u.b) = 20; // { dg-error "accessing" }
+ return u.b;
+}
+constexpr int x2 = test2(); // { dg-message "in .constexpr. expansion" }
+
+constexpr int test3() {
+ U u;
+ u.a = 0;
+ int* p = &u.b;
+ p[u.a] = 10; // { dg-error "accessing" }
+ return u.b;
+}
+constexpr int x3 = test3(); // { dg-message "in .constexpr. expansion" }
+
+constexpr int test4() {
+ U u;
+ u.a = 0;
+ int* p = &u.b;
+ u.a[p] = 10; // { dg-error "accessing" }
+ return u.b;
+}
+constexpr int x4 = test4(); // { dg-message "in .constexpr. expansion" }
+
+struct S { U u[10]; };
+constexpr int test5() {
+ S s;
+ s.u[4].a = 10;
+ 6[s.u].b = 15;
+ return 4[s.u].a + s.u[6].b;
+}
+static_assert(test5() == 25);
+
+constexpr int test6() {
+ U u;
+ u.a = 5;
+ u.c[0] = 3;
+ 1[u.c] = 8;
+ return 1[u.c] + u.c[0];
+}
+static_assert(test6() == 11);
+
+constexpr int test7() {
+ U u; // default initialisation leaves no member initialised
+ int* p = &u.a;
+ *p = 10; // { dg-error "accessing" }
+ return *p;
+}
+constexpr int x7 = test7(); // { dg-message "in .constexpr. expansion" }
+
+constexpr int test8() {
+ U u {}; // value initialisation initialises first member
+ int* p = &u.a;
+ *p = 8;
+ return *p;
+}
+static_assert(test8() == 8);
+
+union V { int :0; static int x; void foo(); int a; };
+constexpr int test9() {
+ V v {}; // should skip zero-width bit fields, static members, and functions
+ int* p = &v.a;
+ *p = 9;
+ return *p;
+}
+static_assert(test9() == 9);
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-union6.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-union6.C
new file mode 100644
index 0000000..00bda53
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-union6.C
@@ -0,0 +1,53 @@
+// { dg-do compile { target c++20 } }
+// PR c++/102286
+
+#include "construct_at.h"
+
+struct S { const int a; int b; };
+union U { int k; S s; };
+
+constexpr int test1() {
+ U u {};
+ std::construct_at(&u.s, S{ 1, 2 });
+ return u.s.b;
+}
+static_assert(test1() == 2);
+
+constexpr int test2() {
+ U u {};
+ int* p = &u.s.b;
+ std::construct_at(p, 5); // { dg-message "in .constexpr. expansion" }
+ return u.s.b;
+}
+constexpr int x2 = test2(); // { dg-message "in .constexpr. expansion" }
+
+constexpr void foo(S* s) {
+ s->b = 10; // { dg-error "accessing .U::s. member instead of initialized .U::k." }
+}
+constexpr int test3() {
+ U u {};
+ foo(&u.s); // { dg-message "in .constexpr. expansion" }
+ return u.s.b;
+}
+constexpr int x3 = test3(); // { dg-message "in .constexpr. expansion" }
+
+struct S2 { int a; int b; };
+union U2 { int k; S2 s; };
+constexpr int test4() {
+ U2 u;
+ int* p = &u.s.b;
+ std::construct_at(p, 8); // { dg-message "in .constexpr. expansion" }
+ return u.s.b;
+};
+constexpr int x4 = test4(); // { dg-message "in .constexpr. expansion" }
+
+constexpr int test5() {
+ union {
+ int data[1];
+ } u;
+ std::construct_at(u.data, 0); // { dg-message "in .constexpr. expansion" }
+ return 0;
+}
+constexpr int x5 = test5(); // { dg-message "in .constexpr. expansion" }
+
+// { dg-error "accessing (uninitialized member|.* member instead of)" "" { target *-*-* } 0 }