On Tue, Sep 08, 2026 at 03:39:09PM -0400, Jason Merrill wrote: > > The init in that case is not a clobber, but an empty CONSTRUCTOR with > > the right flags. Making start_lifetime implied from the > > CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P bit wouldn't be reliable. > > What we could do (but not done in the patch) is use some tree other than > > MODIFY_EXPR/INIT_EXPR with 2+ operands instead of the current MODIFY_EXPR, > > say MODOP_EXPR and let that mean start_lifetime. > > I like the sound of that, but won't insist on it.
Ok, will do that tomorrow. > > I've filed PR127235 for this. It is a preexisting bug (since C++20 > > P1331R2 implementation I guess). Dunno if here it is an error already > > to return the only partially initialized aggregate or only to initialize > > a constexpr var to that. There is > > /* The result of a constexpr function must be completely initialized. > > > > However, in C++20, a constexpr constructor doesn't necessarily have > > to initialize all the fields, so we don't clear > > CONSTRUCTOR_NO_CLEARING > > in order to detect reading an uninitialized object in constexpr > > instead > > of value-initializing it. (reduced_constant_expression_p is expected > > to > > take care of clearing the flag.) */ > > if (TREE_CODE (result) == CONSTRUCTOR > > && (cxx_dialect < cxx20 > > || !DECL_CONSTRUCTOR_P (fun))) > > clear_no_implicit_zero (result); > > and fun here is not a constructor, so we happily clear the > > CONSTRUCTOR_NO_CLEARING bits. Either it is invalid to return something like > > that, in that case we should be actually doing VERIFY_CONSTANT somewhere > > where we currently don't do it, or it is not and then we shouldn't ever call > > clear_no_implicit_zero for C++ >= 20. > > It's non-constant under https://eel.is/c++draft/expr.const#core-2.12 which > was also added by P1331; it's fine for the 'a' variable to be partially > initialized, but not to copy that uninitialized value. The problem is that > by copying the struct as a whole we bypass the uninit check in > cxx_eval_component_reference. > > I guess _store_expression should reject init with CONSTRUCTOR_NO_CLEARING? Is it ok to resolve that incrementally? It is accepts-invalid, so we don't want to backport it though I guess. > > Now, the standard wording isn't clear on what happens when the > > function template is called on an array where its containing subobject > > isn't in lifetime either; the patch acts for the surrounding subobjects > > as a store, so makes all subobjects except for union members and the > > object it is called on within lifetime. If something else should happen > > (e.g. make it non-constant), the patch will need changing. > > That sounds fine to me. Yet another possibility would be to mark all the newly created CONSTRUCTORs for start_lifetime also with CONSTRUCTOR_NO_CLEARING/CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P It all depends on what is the intent. Say #include <memory> #include <type_traits> union U { int a; long b; }; struct A { int c; long d; }; struct B { A e; union U f; int g; }; union V { int h; B i; }; constexpr bool foo () { V j; std::start_lifetime (j.i.e); // The first 5 are clear if (std::is_within_lifetime (&j.i.e.c)) throw 1; if (std::is_within_lifetime (&j.i.e.d)) throw 2; if (std::is_within_lifetime (&j.i.f.a)) throw 3; if (std::is_within_lifetime (&j.i.f.b)) throw 4; if (std::is_within_lifetime (&j.h)) throw 5; // What about these 2 though? Currently in the patch // they are within lifetime, but with setting those 2 bits // on newly created CONSTRUCTORs they wouldn't be. if (!std::is_within_lifetime (&j.i.g)) throw 6; if (!std::is_within_lifetime (&j.i.f)) throw 7; return true; } static_assert (foo ()); Jakub
