[PATCH] c++, libstdc++, v2: Implement C++26 P3726R2 - Adjustments to Union Lifetime Rules [PR125845]
Jakub Jelinek
jakub@redhat.com
Tue Sep 8 20:14:33 GMT 2026
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
More information about the Libstdc++
mailing list