Hi! The following testcase misbehaves, because for std::destroy_at of aggregates (i.e. CLOBBER with CLOBBER_OBJECT_END) we record during constant evaluation just a normal CONSTRUCTOR, so for std::is_within_lifetime it appears as if it is normally within lifetime, even when it is not.
For scalar subobjects and complete objects we use void_node, this patch uses void_node also for the aggregates (but doesn't do that on CLOBBER_OBJECT_START and CLOBBER_STORAGE_START, that breaks too much stuff and the lifetime is just beginning there, not ending). I've just had to tweak cxx_eval_store_expression so that it is able to handle stores into previously outside of lifetime subobjects by making those within lifetime. Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? 2026-09-23 Jakub Jelinek <[email protected]> PR c++/127385 * constexpr.cc (cxx_eval_store_expression): Handle the case of *valp == void_node like if *valp is NULL. (cxx_eval_store_expression): For CLOBBER_OBJECT_END or above store *valp = void_node even for aggregate types. * g++.dg/cpp26/within-lifetime14.C: New test. --- a/gcc/cp/constexpr.cc 2026-09-21 14:31:23.000000000 +0200 +++ b/gcc/cp/constexpr.cc 2026-09-22 15:52:09.649180741 +0200 @@ -8635,7 +8635,7 @@ cxx_eval_store_expression (const constex bool empty_base = false; while (!refs->is_empty ()) { - if (*valp == NULL_TREE) + if (*valp == NULL_TREE || *valp == void_node) { *valp = build_constructor (type, NULL); CONSTRUCTOR_NO_CLEARING (*valp) = no_zero_init; @@ -8984,7 +8984,7 @@ cxx_eval_store_expression (const constex } else if (TREE_CLOBBER_P (init)) { - if (AGGREGATE_TYPE_P (type)) + if (AGGREGATE_TYPE_P (type) && CLOBBER_KIND (init) < CLOBBER_OBJECT_END) { if (*valp && TREE_CODE (*valp) == CONSTRUCTOR) CONSTRUCTOR_ELTS (*valp) = nullptr; --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime14.C 2026-09-22 16:24:27.672059837 +0200 +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime14.C 2026-09-22 17:57:58.477173326 +0200 @@ -0,0 +1,195 @@ +// P3450R1 - Extend std::is_within_lifetime +// { dg-do compile { target c++20 } } + +#include "../cpp2a/construct_at.h" + +namespace std { + template <class T> + consteval bool + is_within_lifetime (const T *p) noexcept + { + return __builtin_is_within_lifetime (p); + } + + template <class T> + constexpr void + start_lifetime (T &r) noexcept + { + __builtin_start_lifetime (__builtin_addressof (r)); + } +} + +struct A { int a, b; }; +struct B { A c, d[2]; }; +union C { B e; int f; }; +struct D { C g; B h; }; +struct E { D i[3]; }; + +consteval int +foo () +{ + E j; + if (!std::is_within_lifetime (&j.i[1])) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e.d[1].b)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].h)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].h.d[0].a)) + return __LINE__; + std::destroy_at (&j.i[1]); + if (std::is_within_lifetime (&j.i[1])) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e.d[1].b)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].h)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (j.i[1]); + if (!std::is_within_lifetime (&j.i[1])) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e.d[1].b)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].h)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (j.i[1].g); + if (!std::is_within_lifetime (&j.i[1])) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e.d[1].b)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].h)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (j.i[1].h); + if (!std::is_within_lifetime (&j.i[1])) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e.d[1].b)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].h)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (j.i[1].h.d); + std::start_lifetime (j.i[1].h.d[0]); + if (std::is_within_lifetime (&j.i[1].h.d[0].a)) + return __LINE__; + j.i[1].h.d[0].a = 42; + if (!std::is_within_lifetime (&j.i[1].h)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (j.i[1].g.e); + std::start_lifetime (j.i[1].g.e.d); + std::start_lifetime (j.i[1].g.e.d[1]); + if (!std::is_within_lifetime (&j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.f)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g.e.d[1])) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e.d[1].b)) + return __LINE__; + j.i[1].g.e.d[1].b = 42; + if (!std::is_within_lifetime (&j.i[1].g.e.d[1])) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g.e.d[1].b)) + return __LINE__; + std::start_lifetime (j.i[1].g); + if (!std::is_within_lifetime (&j.i[1])) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.f)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g.e.d[1].b)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].h)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].h.d[0].a)) + return __LINE__; + std::start_lifetime (j.i[1].g); + j.i[1].g.f = 42; + if (!std::is_within_lifetime (&j.i[1])) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e.d[1].b)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].h)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].h.d[0].a)) + return __LINE__; + std::destroy_at (&j.i[1]); + if (std::is_within_lifetime (&j.i[1])) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.f)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.e.d[1].b)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].h)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].h.d[0].a)) + return __LINE__; + std::construct_at (&j.i[1]); + if (!std::is_within_lifetime (&j.i[1])) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g.e)) + return __LINE__; + if (std::is_within_lifetime (&j.i[1].g.f)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].g.e.d[1].b)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].h)) + return __LINE__; + if (!std::is_within_lifetime (&j.i[1].h.d[0].a)) + return __LINE__; + return 0; +} + +static_assert (foo () == 0); Jakub
