Hi, This is a spin-off of PR103924. The following testcase is rejected by GCC rejected by GCC but accepted by Clang (with libstdc++): https://gcc.godbolt.org/z/63Wss3Ej8 #include <string> constexpr void f(std::initializer_list<std::string>) {} constexpr bool test() { f({"x"}); return true; } static_assert(test()); <source>:10:19: error: non-constant condition for static assertion 10 | static_assert(test()); | ~~~~^~ In file included from /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/string:54, from <source>:1: <source>:10:19: in 'constexpr' expansion of 'test()' <source>:6:6: in 'constexpr' expansion of '((std::__cxx11::basic_string<char>*)<anonymous>)->std::__cxx11::basic_string<char>::~basic_string()' /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/basic_string.h:809:19: in 'constexpr' expansion of '((std::__cxx11::basic_string<char>*)this)->std::__cxx11::basic_string<char>::_M_dispose()' /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/bits/basic_string.h:288:22: error: accessing 'std::__cxx11::basic_string<char>::<unnamed union>::_M_allocated_capacity' member instead of initialized 'std::__cxx11::basic_string<char>::<unnamed union>::_M_local_buf' member in constant expression 288 | _M_destroy(_M_allocated_capacity); | ^~~~~~~~~~~~~~~~~~~~~ Compiler returned: 1 I've been trying to minimize the testcase by removing clutter from basic_string: https://gcc.godbolt.org/z/haxvvr1YK The fun part is that if one deletes the copy constructor (which is actually never called) the code is accepted by GCC. That's what makes me think this is a compiler bug.
As noted PR103924, the testcase is actually accepted by GCC 13 (in C++23 mode), so this looks like a regression. https://gcc.godbolt.org/z/zMK3ceE5M
Started with r14-1705-g2764335bd336f2 "c++: build initializer_list<string> in a loop [PR105838]"
Reduced testcase: namespace std { template <typename T> struct initializer_list { T *_M_array; __SIZE_TYPE__ _M_len; }; } struct A { char *a; union { char b[8]; long c; }; constexpr A (const char *x) : a(b) { for (int i = 0; i < 8; ++i) b[i] = 0; } constexpr ~A () { if (!foo ()) bar (c); } constexpr bool foo () { char *x = a; if (x == b) return true; return false; } constexpr void bar (long) {} }; constexpr void baz (std::initializer_list<A>) { } constexpr bool qux () { baz ({""}); return true; } static_assert (qux (), "");
With minor tweaks to that: --- pr118285-3.C 2025-01-28 16:30:15.798692471 +0100 +++ pr118285-5.C 2025-01-28 16:46:09.572904490 +0100 @@ -6,21 +6,21 @@ template <typename T> struct initializer } struct A { char *a; - union { char b[8]; long c; }; - constexpr A (const char *x) : a(b) + union U { char b[8]; long c; } u; + constexpr A (const char *x) : a(u.b) { for (int i = 0; i < 8; ++i) - b[i] = 0; + u.b[i] = i; } constexpr ~A () { if (!foo ()) - bar (c); + bar (u.c); } constexpr bool foo () { char *x = a; - if (x == b) + if (x == u.b) return true; return false; } so that it avoids using anonymous union just in case and initializes to nonzero the b stuff, if I cxx_eval_constant_expression the whole *this in A::~A(), I get {.a=(char *) &D.2743.u.b, .u={.b={0, 1, 2, 3, 4, 5, 6, 7}}} when evaluated as rvalue and D.2730[0] when evaluated as lvalue. So this is about mismatch with what value the a member has been initialized, so no wonder that foo () returns false.
The extra VAR_DECLs whose addresses are recorded in the initializers are created in #0 make_node (code=VAR_DECL) at ../../gcc/tree.cc:1244 #1 0x0000000001609ce1 in build_decl (loc=4611686018427387913, code=VAR_DECL, name=<tree 0x0>, type=<record_type 0x7fffea2f37e0 A>) at ../../gcc/tree.cc:5421 #2 0x0000000000860eaf in build_local_temp (type=<record_type 0x7fffea2f37e0 A>) at ../../gcc/cp/tree.cc:562 #3 0x00000000008613e5 in build_aggr_init_expr (type=<record_type 0x7fffea2f37e0 A>, init=<call_expr 0x7fffea2f8500>) at ../../gcc/cp/tree.cc:672 #4 0x0000000000861a09 in build_cplus_new (type=<record_type 0x7fffea2f37e0 A>, init=<call_expr 0x7fffea2f8500>, complain=3) at ../../gcc/cp/tree.cc:724 #5 0x000000000042d0fb in convert_like_internal (convs=0x43c0f40, expr=<call_expr 0x7fffea2f8500>, fn=<tree 0x0>, argnum=0, issue_conversion_warnings=true, c_cast_p=false, nested_p=false, complain=3) at ../../gcc/cp/call.cc:8764 #6 0x000000000042f91c in convert_like (convs=0x43c0f40, expr=<array_ref 0x7fffea301bd0>, fn=<tree 0x0>, argnum=0, issue_conversion_warnings=true, c_cast_p=false, nested_p=false, complain=3) at ../../gcc/cp/call.cc:9322 #7 0x000000000042f98d in convert_like (convs=0x43c0f40, expr=<array_ref 0x7fffea301bd0>, complain=3) at ../../gcc/cp/call.cc:9335 #8 0x0000000000442b05 in perform_implicit_conversion_flags (type=<record_type 0x7fffea2f37e0 A>, expr=<array_ref 0x7fffea301bd0>, complain=3, flags=133) at ../../gcc/cp/call.cc:13927 #9 0x000000000049d18c in cxx_eval_vec_init_1 (ctx=0x7fffffffab20, atype=<array_type 0x7fffea2fa0a8>, init=<target_expr 0x7fffea2e4b60>, value_init=false, lval=vc_prvalue, non_constant_p=0x7fffffffd26f, overflow_p=0x7fffffffd26e) at ../../gcc/cp/constexpr.cc:5638 #10 0x000000000049d8ed in cxx_eval_vec_init (ctx=0x7fffffffab20, t=<vec_init_expr 0x7fffea2f1208>, lval=vc_prvalue, non_constant_p=0x7fffffffd26f, overflow_p=0x7fffffffd26e) at ../../gcc/cp/constexpr.cc:5726 #11 0x00000000004a82c9 in cxx_eval_constant_expression (ctx=0x7fffffffab20, t=<vec_init_expr 0x7fffea2f1208>, lval=vc_prvalue, non_constant_p=0x7fffffffd26f, overflow_p=0x7fffffffd26e, jump_target=0x0) at ../../gcc/cp/constexpr.cc:8215 now on namespace std { template <typename T> struct initializer_list { T *_M_array; __SIZE_TYPE__ _M_len; }; } struct A { char *a; union { char b[8]; long c; }; constexpr A (const char *x) : a(b) { for (int i = 0; i < 8; ++i) b[i] = i + x[0]; } constexpr ~A () { if (!foo ()) bar (c); } constexpr bool foo () { char *x = a; if (x == b) return true; return false; } constexpr void bar (long) {} }; constexpr void baz (std::initializer_list<A>) { } constexpr bool qux () { baz ({"1", "2"}); return true; } constexpr bool a = qux (); So, instead of constructing it right into the A array it goes through some temporary.
The trunk branch has been updated by Jason Merrill <jason@gcc.gnu.org>: https://gcc.gnu.org/g:c3b0b98b3ba57840ab2cfbc9d44d001c1e9167bf commit r15-7260-gc3b0b98b3ba57840ab2cfbc9d44d001c1e9167bf Author: Jason Merrill <jason@redhat.com> Date: Tue Jan 28 13:11:50 2025 -0500 c++: constexpr VEC_INIT_EXPR [PR118285] cxx_eval_vec_init_1 was doing the wrong thing for an array of self-referential class type; just evaluating the TARGET_EXPR initializer creates a new object that refers to the TARGET_EXPR_SLOT, if we want it to refer properly to the initialization target we need to provide it. PR c++/118285 gcc/cp/ChangeLog: * constexpr.cc (cxx_eval_vec_init_1): Build INIT_EXPR for initializing a class. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/initlist-opt7.C: New test.
Fixed.
*** Bug 103924 has been marked as a duplicate of this bug. ***
Oops, only fixed on trunk so far.
The releases/gcc-14 branch has been updated by Jason Merrill <jason@gcc.gnu.org>: https://gcc.gnu.org/g:3003d404b436c2dbd5a3a282c25b797d887237d9 commit r14-11346-g3003d404b436c2dbd5a3a282c25b797d887237d9 Author: Jason Merrill <jason@redhat.com> Date: Tue Jan 28 13:11:50 2025 -0500 c++: constexpr VEC_INIT_EXPR [PR118285] cxx_eval_vec_init_1 was doing the wrong thing for an array of self-referential class type; just evaluating the TARGET_EXPR initializer creates a new object that refers to the TARGET_EXPR_SLOT, if we want it to refer properly to the initialization target we need to provide it. PR c++/118285 gcc/cp/ChangeLog: * constexpr.cc (cxx_eval_vec_init_1): Build INIT_EXPR for initializing a class. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/initlist-opt7.C: New test.
The releases/gcc-14 branch has been updated by Jason Merrill <jason@gcc.gnu.org>: https://gcc.gnu.org/g:a72e782528749bf553158039783ce79fa760e3a4 commit r14-11349-ga72e782528749bf553158039783ce79fa760e3a4 Author: Jason Merrill <jason@redhat.com> Date: Tue Jan 28 17:39:41 2025 -0500 c++: disable initializer_list transformation PRs 118673 and 118285 are wrong-code bugs with the transformation to build an initializer_list backing array from a static array of the initializer expressions; let's disable that transformation on the GCC 14 branch. PR c++/118673 PR c++/118285 gcc/cp/ChangeLog: * call.cc (convert_like_internal) [ck_list]: Stop using maybe_init_list_as_array for GCC 14. gcc/testsuite/ChangeLog: * g++.dg/tree-ssa/initlist-opt5.C: Add xfail.
Fixed for 14.3/15.