[PATCH] c++, libstdc++: Implement C++26 P2641R4 - Checking if a union alternative is active
Tomasz Kaminski
tkaminsk@redhat.com
Wed Aug 26 09:50:37 GMT 2026
On Wed, Aug 26, 2026 at 11:17 AM Jakub Jelinek <jakub@redhat.com> wrote:
> Hi!
>
> The following patch attempts to implement this paper (though for now just
> using what the FE prooovides instead of introducing new stuff, see below).
> There is a new builtin which returns true if what the argument points to
> is within lifetime, false if it is not and results in non-constant
> expression if it points to something non-accessible in constant expression,
> or the complete object is not in lifetime. Most of the tests work
> identically in clang++ which implements this paper for some time and g++
> with this patch.
>
> There are a few differences/problems, some of those I'd like to address
> incrementally:
> 1) unlike in clang++, the builtin itself is not consteval, just usable
> in constant expressions; people shouldn't be using the builtin directly
> and when it is called from consteval std::is_within_lifetime (or any
> other consteval function or consteval block), it is guaranteed to be
> immediately evaluated. The builtin if not immediately
> evaluated just quickly checks for some errors (number of arguments and
> that the argument is a pointer) and optimizes itself out. Having
> a consteval builtin would be a novel thing and I'm not convinced it
> is necessary.
> 2) this patch implements just P2641R4, not the subsequent P3450R1 paper.
> The plan is to use
> template<class _Up = void, typename _Tp>
> consteval bool
> is_within_lifetime(const _Tp* __p) noexcept
> { return __builtin_is_within_lifetime (__p)
> && __builtin_constant_p (static_cast <const volatile _Up *>
> (__p)
> && true); }
> afterwards but we need
>
> https://gcc.gnu.org/pipermail/gcc-patches/2026-August/thread.html#726464
> finished for that; I'll restart work on that soon
> 3) we don't implement std::allocator<T>::allocate correctly at constant
> evaluation time, in particular
> https://eel.is/c++draft/memory#allocator.members-5.sentence-2
> That is pretty much the same thing as in P3726R2 std::start_lifetime
> does, therefore my current plan is once this is in, to restart working
> on P3726R2 and add a new CONSTRUCTOR bit next to CONSTRUCTOR_NO_CLEARING
> which would mean the left out elements are not in lifetime even when the
> whole CONSTRUCTOR is, add support for that in
> cxx_eval_is_within_lifetime and implicitly pretend the builtin is
> used in std::allocator<T>::allocate; this is covered in the testsuite
> in within-lifetime3.C but is guarded with #if 0
> 4) the standard says in
> https://eel.is/c++draft/type.traits#meta.const.eval-5
> that "p points to an object that is usable in constant expressions
> or whose complete object's lifetime began within E".
> The patch handles const vars outside of current evaluation (and in that
> case diagnoses/makes non-constant accesses to their mutable members if
> any since those subobjects aren't usable in constant expressions), but
> also diagnoses/making non-constant if the complete object is gone
> (use after scope, use after deallocation, that all seems to me like
> clear UB that should not be constexpr but am not sure that the current
> standard wording is clear about that).
> 5) for complete objects created during E evaluation, the patch uses
> a new get_value_ptr method (the 2 argument one is too much store
> specific, but I can't just use get_value, because I need to
> differentiate
> between values hash map doesn't have an entry for this var yet, it
> clearly has not started lifetime yet, and values hash map has a
> NULL_TREE
> entry for that, that e.g. for scalar means it has started lifetime but
> is uninitialized) and heap_deleted_identifier (for use after delete)
> and is_outside_lifetime (which is use after destruction). The patch
> doesn't track yet though whether the complete subobject actually began
> its lifetime vs. just started being constructed, so there are 3 xfails
> in within-lifetime3.C test which clang++ handles right; for
> "complete object's lifetime began" I think we need some bit somewhere
> set when the evaluation of constructor for the complete object finishes
> (but is unclear whether we should clear it again when we start
> destructor
> though because we don't have consteval destructors yet I'm not sure this
> is actually observable). Also if we can only track it for complete
> objects and not also subobjects. In any case, the problem of not
> implementing this yet means that std::is_within_lifetime uses in ctors
> on the currently constructed subobjects (where complete object is
> not in lifetime yet) won't be non-constant but will return something
> (false/true). I'd like to address this incrementally, but am not yet
> sure where to record it without slowing stuff significantly down and
> causing extra compile time memory use
> 6) thinking about it more, most of the std::is_within_lifetime uses in
> ctors are supposed to be non-constant because the complete object is
> not constructed yet. But if this is a construction done later on
> after the complete subobject is in lifetime already, like in
> // P3450R1 - Extend std::is_within_lifetime
> // { dg-do compile { target c++26 } }
>
> #include <memory>
> #include <type_traits>
>
> struct A {
> consteval A () : a (0), b (0), c (0) {}
> consteval A (int x, int y, int z)
> : a (x), b (y + std::is_within_lifetime (&this->a) * 32
> + std::is_within_lifetime (&this->c) * 64), c (z) {}
> constexpr ~A () {}
> int a, b, c;
> };
>
> consteval int
> foo ()
> {
> A a[2] = {};
> std::destroy_at (&a[0]);
> std::construct_at (&a[0], 4, 5, 6);
> return a[0].a + a[0].b + a[0].c;
> }
>
> static_assert (foo () == 4 + 5 + 6 + 32);
> then clang++ IMHO also handles this correctly but we'd need to
> track in detail in consteval ctors (perhaps for this builtin
> don't need to care about other ctors) which members have been
> constructed already (including vacuous initialization) and which
> haven't been yet
>
> In any case, despite the above limitations I think this patch works
> for most of the intended uses of the new function template and I hope
> the rest can be addressed incrementally.
>
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
>
> 2026-08-26 Jakub Jelinek <jakub@redhat.com>
>
> gcc/cp/
> * cp-tree.h: Implement C++26 P2641R4 - Checking if a union
> alternative is active.
> (enum cp_built_in_function): Add CP_BUILT_IN_IS_WITHIN_LIFETIME.
> (check_builtin_is_within_lifetime): Declare.
> * tree.cc (builtin_valid_in_constant_expr_p): Handle
> CP_BUILT_IN_IS_WITHIN_LIFETIME.
> * decl.cc (cxx_init_decl_processing): Create decl for
> CP_BUILT_IN_IS_WITHIN_LIFETIME.
> * cp-gimplify.cc (cp_gimplify_expr): Handle
> CP_BUILT_IN_IS_WITHIN_LIFETIME.
> * semantics.cc (check_builtin_is_within_lifetime): New function.
> * constexpr.cc (constexpr_global_ctx::get_value): New method.
> (eval_and_check_array_index): Add forward declaration.
> (cxx_eval_is_within_lifetime): New function.
> (cxx_eval_builtin_function_call): Handle
> CP_BUILT_IN_IS_WITHIN_LIFETIME.
> gcc/testsuite/
> * g++.dg/cpp26/within-lifetime1.C: New test.
> * g++.dg/cpp26/within-lifetime2.C: New test.
> * g++.dg/cpp26/within-lifetime3.C: New test.
> * g++.dg/cpp26/within-lifetime4.C: New test.
> * g++.dg/cpp26/within-lifetime5.C: New test.
> * g++.dg/cpp26/within-lifetime6.C: New test.
> * g++.dg/cpp26/within-lifetime7.C: New test.
> libstdc++-v3/
> * include/bits/version.def (within_lifetime): New.
> * include/bits/version.h: Regenerate.
> * include/std/type_traits: Define __glibcxx_want_within_lifetime
> before including bits/version.h.
> (std::is_within_lifetime): New function template.
> * src/c++23/std.cc.in (std::is_within_lifetime): Export.
>
> --- a/gcc/cp/cp-tree.h 2026-08-24 23:45:29.129830790 +0200
> +++ b/gcc/cp/cp-tree.h 2026-08-25 12:05:47.373676908 +0200
> @@ -7262,6 +7262,7 @@ enum cp_built_in_function {
> CP_BUILT_IN_CONSTEXPR_DIAG,
> CP_BUILT_IN_CURRENT_EXCEPTION,
> CP_BUILT_IN_UNCAUGHT_EXCEPTIONS,
> + CP_BUILT_IN_IS_WITHIN_LIFETIME,
> CP_BUILT_IN_LAST
> };
>
> @@ -8686,6 +8687,7 @@ extern tree fold_builtin_is_correspondin
> extern bool pointer_interconvertible_base_of_p (tree, tree, bool = false);
> extern tree fold_builtin_is_pointer_inverconvertible_with_class
> (location_t, int, tree *);
> extern tree fold_builtin_is_string_literal (location_t, int, tree *);
> +extern bool check_builtin_is_within_lifetime (location_t, int, tree *,
> tsubst_flags_t);
> extern tree finish_structured_binding_size (location_t, tree,
> tsubst_flags_t);
> extern tree finish_trait_expr (location_t, enum
> cp_trait_kind,
> tree, tree,
> --- a/gcc/cp/tree.cc 2026-08-24 23:44:49.827085609 +0200
> +++ b/gcc/cp/tree.cc 2026-08-25 12:05:47.375673007 +0200
> @@ -578,6 +578,7 @@ builtin_valid_in_constant_expr_p (const_
> case CP_BUILT_IN_CONSTEXPR_DIAG:
> case CP_BUILT_IN_CURRENT_EXCEPTION:
> case CP_BUILT_IN_UNCAUGHT_EXCEPTIONS:
> + case CP_BUILT_IN_IS_WITHIN_LIFETIME:
> return true;
> default:
> break;
> --- a/gcc/cp/decl.cc 2026-08-24 23:44:57.188987689 +0200
> +++ b/gcc/cp/decl.cc 2026-08-25 12:05:47.377064416 +0200
> @@ -5673,6 +5673,11 @@ cxx_init_decl_processing (void)
> BUILT_IN_FRONTEND, NULL, NULL_TREE);
> set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF);
>
> + decl = add_builtin_function ("__builtin_is_within_lifetime",
> + bool_vaftype,
> CP_BUILT_IN_IS_WITHIN_LIFETIME,
> + BUILT_IN_FRONTEND, NULL, NULL_TREE);
> + set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF);
> +
> integer_two_node = build_int_cst (NULL_TREE, 2);
>
> /* Guess at the initial static decls size. */
> --- a/gcc/cp/cp-gimplify.cc 2026-08-24 23:44:49.788086129 +0200
> +++ b/gcc/cp/cp-gimplify.cc 2026-08-25 12:05:47.378434745 +0200
> @@ -1038,6 +1038,15 @@ cp_gimplify_expr (tree *expr_p, gimple_s
> case CP_BUILT_IN_CONSTEXPR_DIAG:
> *expr_p = void_node;
> break;
> + case CP_BUILT_IN_IS_WITHIN_LIFETIME:
> + check_builtin_is_within_lifetime (EXPR_LOCATION (*expr_p),
> + call_expr_nargs
> (*expr_p),
> + call_expr_nargs (*expr_p)
> + ? &CALL_EXPR_ARG
> (*expr_p, 0)
> + : NULL,
> + tf_warning_or_error);
> + *expr_p = boolean_false_node;
> + break;
> default:
> break;
> }
> --- a/gcc/cp/semantics.cc 2026-08-24 23:44:49.826085623 +0200
> +++ b/gcc/cp/semantics.cc 2026-08-25 14:16:59.977404766 +0200
> @@ -14051,6 +14051,36 @@ fold_builtin_is_string_literal (location
> return boolean_true_node;
> }
>
> +/* Perform __builtin_is_within_lifetime call checking, return false
> + when errors are reported. */
> +
> +bool
> +check_builtin_is_within_lifetime (location_t loc, int nargs, tree *args,
> + tsubst_flags_t complain)
> +{
> + /* Unless users call the builtin directly, the following 2 checks
> should be
> + ensured from std::is_within_lifetime template. */
> + if (nargs != 1)
> + {
> + if (complain & tf_error)
> + error_at (loc, "%<__builtin_is_within_lifetime%> needs a single "
> + "argument");
> + return false;
> + }
> + tree arg = args[0];
> + if (error_operand_p (arg))
> + return false;
> + tree ptype = TREE_TYPE (arg);
> + if (!POINTER_TYPE_P (ptype))
> + {
> + if (complain & tf_error)
> + error_at (loc, "%<__builtin_is_within_lifetime%> argument type %qT
> "
> + "is not pointer type", ptype);
> + return false;
> + }
> + return true;
> +}
> +
> /* [basic.types] 8. True iff TYPE is an object type. */
>
> static bool
> --- a/gcc/cp/constexpr.cc 2026-08-24 23:44:49.788086129 +0200
> +++ b/gcc/cp/constexpr.cc 2026-08-25 19:26:24.647145859 +0200
> @@ -1253,6 +1253,13 @@ public:
> return *p;
> return NULL_TREE;
> }
> + tree *get_value_ptr (tree t)
> + {
> + if (tree *p = values.get (t))
> + if (*p != void_node)
> + return p;
> + return nullptr;
> + }
> tree *get_value_ptr (tree t, bool initializing)
> {
> if (modifiable && !modifiable->contains (t))
> @@ -2533,6 +2540,256 @@ cxx_eval_constexpr_diag (const constexpr
> return void_node;
> }
>
> +static tree eval_and_check_array_index (const constexpr_ctx *, tree, bool,
> + bool *, bool *, tree *);
> +
> +/* Attempt to evaluate T which represents a call to
> + __builtin_is_within_lifetime. */
> +
> +static tree
> +cxx_eval_is_within_lifetime (const constexpr_ctx *ctx, tree t,
> + bool *non_constant_p, bool *overflow_p,
> + tree *jump_target)
> +{
> + location_t loc = EXPR_LOCATION (t);
> + if (!check_builtin_is_within_lifetime (loc, call_expr_nargs (t),
> + call_expr_nargs (t)
> + ? &CALL_EXPR_ARG (t, 0) : NULL,
> + ctx->quiet ? tf_none
> + : tf_warning_or_error))
> + {
> + *non_constant_p = true;
> + return t;
> + }
> + tree arg = CALL_EXPR_ARG (t, 0);
> + arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue,
> + non_constant_p, overflow_p,
> + jump_target);
> + if (*jump_target)
> + return NULL_TREE;
> + if (*non_constant_p)
> + return t;
> + /* Need to strip casts to pointers to void, but should preserve
> + pointer casts within the innermost pointer to void cast if
> + any, so that e.g. pointers to heap allocations are handled
> + correctly. */
> + tree p = arg;
> + while (CONVERT_EXPR_P (p) || TREE_CODE (p) == NON_LVALUE_EXPR)
> + {
> + if (!TYPE_PTR_P (TREE_TYPE (p)))
> + break;
> + if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (p))))
> + arg = TREE_OPERAND (p, 0);
> + p = TREE_OPERAND (p, 0);
> + }
> + if (integer_zerop (arg))
> + {
> + if (!ctx->quiet)
> + error_at (loc, "%qs called with a null pointer",
> + "__builtin_within_lifetime");
> + *non_constant_p = true;
> + return t;
> + }
> + if (POINTER_TYPE_P (TREE_TYPE (arg))
> + && (TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == FUNCTION_TYPE
> + || TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == METHOD_TYPE))
> + {
> + if (!ctx->quiet)
> + error_at (loc, "%qs called with pointer to function",
> + "__builtin_within_lifetime");
> + *non_constant_p = true;
> + return t;
> + }
> + arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg);
> + arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue,
> + non_constant_p, overflow_p,
> + jump_target);
> + if (*jump_target)
> + return NULL_TREE;
> + if (*non_constant_p)
> + return t;
> + auto_vec <tree, 4> refs;
> + tree obj;
> + for (obj = arg; ; obj = TREE_OPERAND (obj, 0))
> + {
> + switch (TREE_CODE (obj))
> + {
> + case COMPONENT_REF:
> + case ARRAY_REF:
> + case REALPART_EXPR:
> + case IMAGPART_EXPR:
> + refs.safe_push (obj);
> + continue;
> + default:
> + break;
> + }
> + break;
> + }
> + tree *valp = nullptr;
> + bool check_mutable = false;
> + if (DECL_P (obj))
> + {
> + valp = ctx->global->get_value_ptr (obj);
> + if (!valp
> + && VAR_P (obj)
> + && TREE_STATIC (obj)
> + && decl_constant_var_p (obj))
> + {
> + valp = &DECL_INITIAL (obj);
> + check_mutable = true;
> + }
> + }
> + if (!valp)
> + {
> + if (!ctx->quiet)
> + {
> + auto_diagnostic_group d;
> + if (DECL_P (obj) && DECL_NAME (obj) == heap_deleted_identifier)
> + {
> + error_at (loc, "%qs on allocated storage after deallocation "
> + "is not a constant expression",
> + "__builtin_within_lifetime");
> + inform (DECL_SOURCE_LOCATION (obj), "allocated here");
> + }
> + else if (DECL_P (obj) && ctx->global->is_outside_lifetime (obj))
> + {
> + error_at (loc, "%qs on %qE outside its lifetime is not a "
> + "constant expression",
> + "__builtin_within_lifetime", obj);
> + inform (DECL_SOURCE_LOCATION (obj), "declared here");
> + }
> + else
> + error_at (loc, "%qs on %qE from outside current evaluation "
> + "is not a constant expression",
> + "__builtin_within_lifetime", obj);
> + }
> + *non_constant_p = true;
> + return t;
> + }
> + tree val = *valp;
> + unsigned i;
> + tree ref;
> + /* In the loop below, val contains the value of the current
> + container (usually a CONSTRUCTOR), or NULL_TREE if we are in zero
> + initialized container (i.e. first union member is in lifetime),
> + or void_list_node if we are in uninitialized container
> + (non-union is still within lifetime, none of the union members are
> + within lifetime. */
> + if (val == NULL_TREE)
> + val = void_list_node;
> + FOR_EACH_VEC_ELT_REVERSE (refs, i, ref)
> + switch (TREE_CODE (ref))
> + {
> + case REALPART_EXPR:
> + case IMAGPART_EXPR:
> + if (val
> + && TREE_CODE (val) == COMPLEX_EXPR
> + && (TREE_OPERAND (val, TREE_CODE (ref) == IMAGPART_EXPR)
> + == void_node))
> + return boolean_false_node;
> + return boolean_true_node;
> + case COMPONENT_REF:
> + if (check_mutable && DECL_MUTABLE_P (TREE_OPERAND (ref, 1)))
> + {
> + if (!ctx->quiet)
> + error_at (loc, "%qs on %<mutable%> sub-object %qD",
> + "__builtin_within_lifetime", TREE_OPERAND (ref,
> 1));
> + *non_constant_p = true;
> + return t;
> + }
> + if (TREE_CODE (TREE_TYPE (TREE_OPERAND (ref, 0))) == UNION_TYPE)
> + {
> + tree union_type = TREE_TYPE (TREE_OPERAND (ref, 0));
> + if (val == NULL_TREE)
> + {
> + if (TREE_OPERAND (ref, 1)
> + != next_aggregate_field (TYPE_FIELDS (union_type)))
> + return boolean_false_node;
> + continue;
> + }
> + else
> + {
> + if (TREE_CODE (val) != CONSTRUCTOR)
> + return boolean_false_node;
> + if (CONSTRUCTOR_NELTS (val) == 0)
> + {
> + if (CONSTRUCTOR_NO_CLEARING (val))
> + return boolean_false_node;
> + tree first
> + = next_aggregate_field (TYPE_FIELDS (union_type));
> + if (first != TREE_OPERAND (ref, 1))
> + return boolean_false_node;
> + val = NULL_TREE;
> + continue;
> + }
> + else
> + {
> + if (CONSTRUCTOR_ELT (val, 0)->index
> + != TREE_OPERAND (ref, 1))
> + return boolean_false_node;
> + val = CONSTRUCTOR_ELT (val, 0)->value;
> + if (val == void_node)
> + return boolean_false_node;
> + continue;
> + }
> + }
> + }
> + if (val == NULL_TREE || val == void_list_node)
> + continue;
> + if (TREE_CODE (val) != CONSTRUCTOR)
> + return boolean_false_node;
> + unsigned int j;
> + tree field, value;
> + FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (val), j, field, value)
> + if (field == TREE_OPERAND (ref, 1))
> + {
> + if (value == void_node)
> + return boolean_false_node;
> + val = value;
> + ref = NULL_TREE;
> + break;
> + }
> + if (ref == NULL_TREE)
> + continue;
> + if (CONSTRUCTOR_NO_CLEARING (val))
> + val = void_list_node;
> + else
> + val = NULL_TREE;
> + continue;
> + case ARRAY_REF:
> + field = eval_and_check_array_index (ctx, ref, false,
> + non_constant_p, overflow_p,
> + jump_target);
> + if (*jump_target)
> + return NULL_TREE;
> + if (*non_constant_p)
> + return t;
> + if (val == NULL_TREE || val == void_list_node)
> + continue;
> + if (TREE_CODE (val) == STRING_CST)
> + return boolean_true_node;
> + if (TREE_CODE (val) != CONSTRUCTOR)
> + return boolean_false_node;
> + HOST_WIDE_INT idx;
> + idx = find_array_ctor_elt (val, field, false);
> + if (idx != -1)
> + {
> + val = CONSTRUCTOR_ELT (val, idx)->value;
> + if (val == void_node)
> + return boolean_false_node;
> + continue;
> + }
> + if (CONSTRUCTOR_NO_CLEARING (val))
> + val = void_list_node;
> + else
> + val = NULL_TREE;
> + continue;
> + default:
> + gcc_unreachable ();
> + }
> + return boolean_true_node;
> +}
> +
> /* Attempt to evaluate T which represents a call to a builtin function.
> We assume here that all builtin functions evaluate to scalar types
> represented by _CST nodes. */
> @@ -2606,6 +2863,10 @@ cxx_eval_builtin_function_call (const co
> return cxx_eval_constexpr_diag (ctx, t, non_constant_p, overflow_p,
> jump_target);
>
> + case CP_BUILT_IN_IS_WITHIN_LIFETIME:
> + return cxx_eval_is_within_lifetime (ctx, t, non_constant_p,
> overflow_p,
> + jump_target);
> +
> default:
> break;
> }
> --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime1.C 2026-08-25
> 12:05:47.381287872 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime1.C 2026-08-25
> 16:58:17.075473131 +0200
> @@ -0,0 +1,341 @@
> +// P3450R1 - Extend std::is_within_lifetime
> +// { dg-do compile { target c++20 } }
> +
> +#if __has_builtin(__builtin_is_within_lifetime)
> +namespace std {
> + template <class T>
> + consteval bool
> + is_within_lifetime (const T *p) noexcept
> + {
> + return __builtin_is_within_lifetime (p);
> + }
> +}
> +#endif
> +
> +namespace std {
> + template <typename T, typename F>
> + constexpr T
> + bit_cast (const F &f) noexcept
> + {
> + return __builtin_bit_cast (T, f);
> + }
> +}
> +
> +constexpr char d = 0;
> +struct E { char a; union F { int b; long c; short d; struct G { int e; }
> f[2]; } g; };
> +constexpr E e = {};
> +constexpr E f = { .a = 1, .g = { .f = {} } };
> +constexpr int s = 42;
> +constexpr int t[2] = {};
> +
> +consteval int
> +foo ()
> +{
> + char a = 0;
> + struct B { int b; } b = {};
> + struct C : B { int c[2]; union D { int d; long e; } f; } c = {};
> + union H { union I { int a; union J { int b; long c; } d; } e; long f; }
> g = {};
> + H h;
> + C i;
> + char j;
> + bool k = true;
> + if (!std::is_within_lifetime (&a))
> + return 1;
> + if (!std::is_within_lifetime (&b))
> + return 2;
> + if (!std::is_within_lifetime (&b.b))
> + return 3;
> + if (!std::is_within_lifetime (&c))
> + return 4;
> + if (!std::is_within_lifetime (&c.b))
> + return 5;
>
Any reason not to check &c.c (array) itself>
> + if (!std::is_within_lifetime (&c.c[1]))
> + return 6;
> + if (!std::is_within_lifetime (&c.f))
> + return 7;
> + if (!std::is_within_lifetime (&c.f.d))
> + return 8;
> + if (std::is_within_lifetime (&c.f.e))
> + return 9;
> + if (!std::is_within_lifetime (&d))
> + return 10;
> + if (!std::is_within_lifetime (&e))
> + return 11;
> + if (!std::is_within_lifetime (&e.a))
> + return 12;
> + if (!std::is_within_lifetime (&e.g))
> + return 13;
> + if (!std::is_within_lifetime (&e.g.b))
> + return 14;
> + if (std::is_within_lifetime (&e.g.c))
> + return 15;
> + if (std::is_within_lifetime (&e.g.d))
> + return 16;
> + if (std::is_within_lifetime (&e.g.f))
> + return 17;
> + if (!std::is_within_lifetime (&f))
> + return 18;
> + if (!std::is_within_lifetime (&f.a))
> + return 19;
> + if (!std::is_within_lifetime (&f.g))
> + return 20;
> + if (std::is_within_lifetime (&f.g.b))
> + return 21;
> + if (std::is_within_lifetime (&f.g.c))
> + return 22;
> + if (std::is_within_lifetime (&f.g.d))
> + return 23;
> + if (!std::is_within_lifetime (&f.g.f))
> + return 24;
> + if (!std::is_within_lifetime (&f.g.f[0]))
> + return 25;
> + if (!std::is_within_lifetime (&f.g.f[0].e))
> + return 26;
> + if (!std::is_within_lifetime (&f.g.f[1].e))
> + return 27;
> + if (!std::is_within_lifetime (&g))
> + return 28;
> + if (!std::is_within_lifetime (&g.e))
> + return 29;
> + if (std::is_within_lifetime (&g.f))
> + return 30;
> + if (!std::is_within_lifetime (&g.e.a))
> + return 31;
> + if (std::is_within_lifetime (&g.e.d))
> + return 32;
> + g.e.d.c = 1;
+ if (!std::is_within_lifetime (&g))
> + return 33;
> + if (!std::is_within_lifetime (&g.e))
> + return 34;
> + if (std::is_within_lifetime (&g.f))
> + return 35;
> + if (std::is_within_lifetime (&g.e.a))
> + return 36;
> + if (!std::is_within_lifetime (&g.e.d))
> + return 37;
> + if (std::is_within_lifetime (&g.e.d.b))
+ return 38;
> + if (!std::is_within_lifetime (&g.e.d.c))
> + return 39;
> + g.f = 42;
> + if (!std::is_within_lifetime (&g))
> + return 40;
> + if (std::is_within_lifetime (&g.e))
> + return 41;
> + if (!std::is_within_lifetime (&g.f))
> + return 42;
> + if (!std::is_within_lifetime (&h))
> + return 43;
> + if (std::is_within_lifetime (&h.e))
> + return 44;
> + if (std::is_within_lifetime (&h.f))
> + return 45;
> + if (!std::is_within_lifetime (&i))
> + return 46;
> + if (!std::is_within_lifetime (&i.b))
> + return 47;
> + if (!std::is_within_lifetime (&i.c[1]))
> + return 48;
> + if (!std::is_within_lifetime (&i.f))
> + return 49;
> + if (std::is_within_lifetime (&i.f.d))
> + return 50;
> + if (std::is_within_lifetime (&i.f.e))
> + return 51;
> + if (!std::is_within_lifetime (&j))
> + return 52;
> + if (!std::is_within_lifetime (&k))
> + return 53;
> + unsigned char l = std::bit_cast <unsigned char> (k);
> + if (!std::is_within_lifetime (&l))
> + return 54;
> + struct {} m;
> + if (!std::is_within_lifetime (&m))
> + return 55;
> + unsigned char n = std::bit_cast <unsigned char> (m);
> + if (!std::is_within_lifetime (&n))
> + return 56;
> + int o;
> + if (!std::is_within_lifetime (static_cast <volatile int *> (&o)))
> + return 57;
> + if (!std::is_within_lifetime (static_cast <void *> (&o)))
> + return 58;
> + volatile int p;
> + if (!std::is_within_lifetime (const_cast <int *> (&p)))
> + return 59;
> + struct { union { union { struct { int a; long b; } c; int d; } e; int
> f; } g; int h; } q;
> + if (!std::is_within_lifetime (&q))
> + return 60;
> + if (!std::is_within_lifetime (&q.g))
> + return 61;
> + if (!std::is_within_lifetime (&q.h))
> + return 62;
> + if (std::is_within_lifetime (&q.g.e))
> + return 63;
> + if (std::is_within_lifetime (&q.g.f))
> + return 64;
> + if (std::is_within_lifetime (&q.g.e.c))
> + return 65;
> + if (std::is_within_lifetime (&q.g.e.d))
> + return 66;
> + if (std::is_within_lifetime (&q.g.e.c.a))
> + return 67;
> + if (std::is_within_lifetime (&q.g.e.c.b))
> + return 68;
> + if (!std::is_within_lifetime (static_cast <void *> (&q)))
> + return 69;
>
+ if (!std::is_within_lifetime (static_cast <void *> (&q.g)))
> + return 70;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.h)))
> + return 71;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e)))
> + return 72;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.f)))
> + return 73;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c)))
> + return 74;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.d)))
> + return 75;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a)))
> + return 76;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b)))
> + return 77;
> + q.g.f = 0;
> + if (!std::is_within_lifetime (&q))
> + return 78;
> + if (!std::is_within_lifetime (&q.g))
> + return 79;
> + if (!std::is_within_lifetime (&q.h))
> + return 80;
> + if (std::is_within_lifetime (&q.g.e))
> + return 81;
> + if (!std::is_within_lifetime (&q.g.f))
> + return 82;
> + if (std::is_within_lifetime (&q.g.e.c))
> + return 83;
> + if (std::is_within_lifetime (&q.g.e.d))
> + return 84;
> + if (std::is_within_lifetime (&q.g.e.c.a))
> + return 85;
> + if (std::is_within_lifetime (&q.g.e.c.b))
> + return 86;
> + if (!std::is_within_lifetime (static_cast <void *> (&q)))
> + return 87;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.g)))
> + return 88;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.h)))
> + return 89;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e)))
> + return 90;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.g.f)))
> + return 91;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c)))
> + return 92;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.d)))
> + return 93;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a)))
> + return 94;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b)))
> + return 95;
> + q.g.e.d = 0;
> + if (!std::is_within_lifetime (&q))
> + return 96;
> + if (!std::is_within_lifetime (&q.g))
> + return 97;
> + if (!std::is_within_lifetime (&q.h))
> + return 98;
> + if (!std::is_within_lifetime (&q.g.e))
> + return 99;
> + if (std::is_within_lifetime (&q.g.f))
> + return 100;
> + if (std::is_within_lifetime (&q.g.e.c))
> + return 101;
> + if (!std::is_within_lifetime (&q.g.e.d))
> + return 102;
> + if (std::is_within_lifetime (&q.g.e.c.a))
> + return 103;
> + if (std::is_within_lifetime (&q.g.e.c.b))
> + return 104;
> + if (!std::is_within_lifetime (static_cast <void *> (&q)))
> + return 105;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.g)))
> + return 106;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.h)))
> + return 107;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e)))
> + return 108;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.f)))
> + return 109;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c)))
> + return 110;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.d)))
> + return 111;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a)))
> + return 112;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b)))
> + return 113;
> + q.g.e.c.a = 0;
> + if (!std::is_within_lifetime (&q))
> + return 114;
> + if (!std::is_within_lifetime (&q.g))
> + return 115;
> + if (!std::is_within_lifetime (&q.h))
> + return 116;
> + if (!std::is_within_lifetime (&q.g.e))
> + return 117;
> + if (std::is_within_lifetime (&q.g.f))
> + return 118;
> + if (!std::is_within_lifetime (&q.g.e.c))
> + return 119;
> + if (std::is_within_lifetime (&q.g.e.d))
> + return 120;
> + if (!std::is_within_lifetime (&q.g.e.c.a))
> + return 121;
> + if (!std::is_within_lifetime (&q.g.e.c.b))
> + return 122;
>
I was just going to ask for this test case; thanks for adding it.
> + if (!std::is_within_lifetime (static_cast <void *> (&q)))
> + return 123;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.g)))
> + return 124;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.h)))
> + return 125;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e)))
> + return 126;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.f)))
> + return 127;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.c)))
> + return 128;
> + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.d)))
> + return 129;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a)))
> + return 130;
> + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b)))
> + return 131;
> + struct { union { int a; short b; }; mutable int c; } r = { .b = 42 };
> + if (!std::is_within_lifetime (&r))
> + return 132;
> + if (std::is_within_lifetime (&r.a))
> + return 133;
> + if (!std::is_within_lifetime (&r.b))
> + return 134;
> + if (!std::is_within_lifetime (&r.c))
> + return 135;
> + if (!std::is_within_lifetime (&s))
> + return 136;
> + if (!std::is_within_lifetime (const_cast <int *> (&s)))
> + return 137;
> + if (!std::is_within_lifetime (const_cast <volatile int *> (&s)))
> + return 138;
> + if (!std::is_within_lifetime (static_cast <const void *> (&s)))
> + return 139;
> + if (!std::is_within_lifetime (t))
> + return 140;
> + if (!std::is_within_lifetime (t + 0))
> + return 141;
> + if (!std::is_within_lifetime (t + 1))
> + return 142;
> + return 0;
> +}
> +
> +static_assert (foo () == 0);
> --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime2.C 2026-08-25
> 12:05:47.381392859 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime2.C 2026-08-25
> 12:05:47.381392859 +0200
> @@ -0,0 +1,65 @@
> +// 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);
> + }
> +}
> +
> +consteval bool
> +foo (int &x, char &y)
> +{
> + if (std::is_within_lifetime (&x) || std::is_within_lifetime (&y))
> + return false;
> + std::construct_at (&y, 42);
> + if (std::is_within_lifetime (&x) || !std::is_within_lifetime (&y))
> + return false;
> + std::construct_at (&x, 41);
> + if (!std::is_within_lifetime (&x) || std::is_within_lifetime (&y))
> + return false;
> + return true;
> +}
> +
> +static_assert ([] { union { int a; char b; } u; return foo (u.a, u.b); }
> ());
> +static_assert ([] { union { int a; char b; }; return foo (a, b); } ());
> +static_assert ([] { struct { union { int a; char b; }; } s; return foo
> (s.a, s.b); } ());
> +static_assert ([] { struct { union { int a; char b; } u; } s; return foo
> (s.u.a, s.u.b); } ());
> +
> +consteval bool
> +bar ()
> +{
> + union { union { int a; long b; } c; short d; };
> + if (std::is_within_lifetime (&d)
> + || std::is_within_lifetime (&c)
> + || std::is_within_lifetime (&c.a)
> + || std::is_within_lifetime (&c.b))
> + return false;
> + std::construct_at (&d);
> + if (!std::is_within_lifetime (&d)
> + || std::is_within_lifetime (&c)
> + || std::is_within_lifetime (&c.a)
> + || std::is_within_lifetime (&c.b))
> + return false;
> + std::construct_at (&c);
> + std::construct_at (&c.b);
> + if (std::is_within_lifetime (&d)
> + || !std::is_within_lifetime (&c)
> + || std::is_within_lifetime (&c.a)
> + || !std::is_within_lifetime (&c.b))
> + return false;
> + std::construct_at (&c.a);
> + if (std::is_within_lifetime (&d)
> + || !std::is_within_lifetime (&c)
> + || !std::is_within_lifetime (&c.a)
> + || std::is_within_lifetime (&c.b))
> + return false;
> + return true;
> +}
> +
> +static_assert (bar ());
> --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C 2026-08-25
> 12:05:47.381492104 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C 2026-08-25
> 17:45:30.229298154 +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);
> + }
> +}
> +
> +consteval bool
> +foo (bool x)
> +{
> + std::allocator <int> a;
> + auto b = a.allocate (1);
> +#if 0
> + // [allocator.members]/5 says it should start lifetime of the array
> + // but not its elements, and b points to the first element.
> + if (std::is_within_lifetime (b))
> + return false;
> +#endif
> + std::construct_at (b);
> + if (!std::is_within_lifetime (b))
> + return false;
> + std::destroy_at (b);
> + if (std::is_within_lifetime (b))
> + return false;
> + a.deallocate (b, 1);
> + if (x)
> + __builtin_is_within_lifetime (b); // { dg-error
> "'__builtin_within_lifetime' on allocated storage after deallocation is not
> a constant expression" }
> + return true;
> +}
> +
> +static_assert (foo (false));
> +bool a = foo (true); // { dg-error "call to consteval
> function 'foo\\\(true\\\)' is not a constant expression" }
> +
> +consteval bool
> +bar (int x)
> +{
> + int *a;
> + decltype (nullptr) *b;
> + {
> + int c = 42;
> + a = &c;
> + if (!std::is_within_lifetime (a))
> + return false;
> + }
> + {
> + int c = 42;
> + if (x == 1)
> + __builtin_is_within_lifetime (a); // { dg-error
> "'__builtin_within_lifetime' on 'c' outside its lifetime is not a constant
> expression" }
>
That's message should very confusing, '__builtin_within_lifetime' on 'c'
outside its lifetime is not a constant expression", while
the purpose of this function is to check if they are within the lifetime.
Maybe we should say outside it's storage duration?
> + }
> + if (x == 2)
> + __builtin_is_within_lifetime (a); // { dg-error
> "'__builtin_within_lifetime' on 'c' outside its lifetime is not a constant
> expression" }
> + {
> + int c[42] = {};
> + a = &c[40];
> + if (!std::is_within_lifetime (a))
> + return false;
> + }
> + if (x == 3)
> + __builtin_is_within_lifetime (a); // { dg-error
> "'__builtin_within_lifetime' on 'c' outside its lifetime is not a constant
> expression" }
> + {
> + decltype (nullptr) d = nullptr;
> + b = &d;
> + if (!std::is_within_lifetime (b))
> + return false;
> + }
> + {
> + decltype (nullptr) d = nullptr;
> + if (x == 4)
> + __builtin_is_within_lifetime (b); // { dg-error
> "'__builtin_within_lifetime' on 'd' outside its lifetime is not a constant
> expression" }
> + }
> + if (x == 5)
> + __builtin_is_within_lifetime (b); // { dg-error
> "'__builtin_within_lifetime' on 'd' outside its lifetime is not a constant
> expression" }
> + {
> + decltype (nullptr) d[42] = {};
> + b = &d[40];
> + if (!std::is_within_lifetime (b))
> + return false;
> + }
> + if (x == 6)
> + __builtin_is_within_lifetime (b); // { dg-error
> "'__builtin_within_lifetime' on 'd' outside its lifetime is not a constant
> expression" }
> + {
> + int c;
> + a = &c;
> + if (!std::is_within_lifetime (a))
> + return false;
> + }
> + {
> + int c;
> + if (x == 7)
> + __builtin_is_within_lifetime (a); // { dg-error
> "'__builtin_within_lifetime' on 'c' outside its lifetime is not a constant
> expression" }
> + }
> + if (x == 8)
> + __builtin_is_within_lifetime (a); // { dg-error
> "'__builtin_within_lifetime' on 'c' outside its lifetime is not a constant
> expression" }
> + {
> + int c[42];
> + a = &c[40];
> + if (!std::is_within_lifetime (a))
> + return false;
> + }
> + if (x == 9)
> + __builtin_is_within_lifetime (a); // { dg-error
> "'__builtin_within_lifetime' on 'c' outside its lifetime is not a constant
> expression" }
> + {
> + decltype (nullptr) d;
> + b = &d;
> + if (!std::is_within_lifetime (b))
> + return false;
> + }
> + {
> + decltype (nullptr) d;
> + if (x == 10)
> + __builtin_is_within_lifetime (b); // { dg-error
> "'__builtin_within_lifetime' on 'd' outside its lifetime is not a constant
> expression" }
> + }
> + if (x == 11)
> + __builtin_is_within_lifetime (b); // { dg-error
> "'__builtin_within_lifetime' on 'd' outside its lifetime is not a constant
> expression" }
> + {
> + decltype (nullptr) d[42];
> + b = &d[40];
> + if (!std::is_within_lifetime (b))
> + return false;
> + }
> + if (x == 12)
> + __builtin_is_within_lifetime (b); // { dg-error
> "'__builtin_within_lifetime' on 'd' outside its lifetime is not a constant
> expression" }
> + int e[2];
> + if (!std::is_within_lifetime (&e)
> + || !std::is_within_lifetime (&e[0])
> + || !std::is_within_lifetime (&e[1]))
> + return true;
> + std::destroy_at (&e[0]);
> + if (!std::is_within_lifetime (&e)
> + || std::is_within_lifetime (&e[0])
> + || !std::is_within_lifetime (&e[1]))
> + return false;
> + std::construct_at (&e[0]);
> + std::destroy_at (&e[1]);
> + if (!std::is_within_lifetime (&e)
> + || !std::is_within_lifetime (&e[0])
> + || std::is_within_lifetime (&e[1]))
> + return false;
> + std::construct_at (&e[1]);
> + if (!std::is_within_lifetime (&e)
> + || !std::is_within_lifetime (&e[0])
> + || !std::is_within_lifetime (&e[1]))
> + return false;
> + struct { int a, b; } f;
> + if (!std::is_within_lifetime (&f)
> + || !std::is_within_lifetime (&f.a)
> + || !std::is_within_lifetime (&f.b))
> + return true;
> + std::destroy_at (&f.a);
> + if (!std::is_within_lifetime (&f)
> + || std::is_within_lifetime (&f.a)
> + || !std::is_within_lifetime (&f.b))
> + return false;
> + std::construct_at (&f.a);
> + std::destroy_at (&f.b);
> + if (!std::is_within_lifetime (&f)
> + || !std::is_within_lifetime (&f.a)
> + || std::is_within_lifetime (&f.b))
> + return false;
> + std::construct_at (&f.b);
> + if (!std::is_within_lifetime (&f)
> + || !std::is_within_lifetime (&f.a)
> + || !std::is_within_lifetime (&f.b))
> + return false;
> + return true;
> +}
> +
> +static_assert (bar (0));
> +bool b = bar (1); // { dg-error "call to consteval
> function 'bar\\\(1\\\)' is not a constant expression" }
> +bool c = bar (2); // { dg-error "call to consteval
> function 'bar\\\(2\\\)' is not a constant expression" }
> +bool d = bar (3); // { dg-error "call to consteval
> function 'bar\\\(3\\\)' is not a constant expression" }
> +bool e = bar (4); // { dg-error "call to consteval
> function 'bar\\\(4\\\)' is not a constant expression" }
> +bool f = bar (5); // { dg-error "call to consteval
> function 'bar\\\(5\\\)' is not a constant expression" }
> +bool g = bar (6); // { dg-error "call to consteval
> function 'bar\\\(6\\\)' is not a constant expression" }
> +bool h = bar (7); // { dg-error "call to consteval
> function 'bar\\\(7\\\)' is not a constant expression" }
> +bool i = bar (8); // { dg-error "call to consteval
> function 'bar\\\(8\\\)' is not a constant expression" }
> +bool j = bar (9); // { dg-error "call to consteval
> function 'bar\\\(9\\\)' is not a constant expression" }
> +bool k = bar (10); // { dg-error "call to consteval
> function 'bar\\\(10\\\)' is not a constant expression" }
> +bool l = bar (11); // { dg-error "call to consteval
> function 'bar\\\(11\\\)' is not a constant expression" }
> +bool m = bar (12); // { dg-error "call to consteval
> function 'bar\\\(12\\\)' is not a constant expression" }
> +
> +struct A { int a = 42; bool b = std::is_within_lifetime (&a); }; //
> { dg-error "" "" { xfail *-*-* } }
>
What are the errors here and for next example? The member lifetime seems
clear at corresponding points.
> +constexpr A n;
>
I.e. a is in lifetime here.
> +
> +struct B { consteval B () : a (42), b (std::is_within_lifetime (&a)) {}
> int a; bool b; };
>
And not in lifetime here.
> +constexpr B o; // { dg-error "" "" { xfail *-*-*
> } }
> +
> +struct C { consteval C () { __builtin_is_within_lifetime (this); } } p;
> // { dg-error "" "" { xfail *-*-* } }
>
Here, this object is under construction, and I remember this being unclear.
> --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime4.C 2026-08-25
> 15:27:19.687843572 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime4.C 2026-08-25
> 19:11:05.217203640 +0200
> @@ -0,0 +1,156 @@
> +// P3450R1 - Extend std::is_within_lifetime
> +// { dg-do compile { target c++20 } }
> +
> +template <typename T>
> +consteval bool baz (const T *p) { return __builtin_is_within_lifetime
> (p); }
> +// { dg-error "'__builtin_within_lifetime' on 'a' from outside current
> evaluation is not a constant expression" "" { target *-*-* } .-1 }
> +bool a = baz (&a); // { dg-error "is not a
> constant expression" }
> +constexpr bool b = false;
> +
> +void
> +foo (int *p)
> +{
> + __builtin_is_within_lifetime (&b);
> + __builtin_is_within_lifetime (nullptr);
> + __builtin_is_within_lifetime (static_cast <int *> (nullptr));
> + __builtin_is_within_lifetime (&foo);
> + __builtin_is_within_lifetime (0); // { dg-error
> "'__builtin_is_within_lifetime' argument type 'int' is not pointer type" }
> + __builtin_is_within_lifetime (0.0); // { dg-error
> "'__builtin_is_within_lifetime' argument type 'double' is not pointer type"
> }
> + __builtin_is_within_lifetime (); // { dg-error
> "'__builtin_is_within_lifetime' needs a single argument" }
> + __builtin_is_within_lifetime (&b, &b); // { dg-error
> "'__builtin_is_within_lifetime' needs a single argument" }
> + __builtin_is_within_lifetime (p);
> +}
> +
> +extern int v;
> +
> +consteval bool
> +bar (int x)
> +{
> + switch (x)
> + {
> + case 0: __builtin_is_within_lifetime (&b); break;
> + case 1: __builtin_is_within_lifetime (nullptr); break; // { dg-error
> "'__builtin_within_lifetime' called with a null pointer" }
> + case 2: __builtin_is_within_lifetime (static_cast <int *> (nullptr));
> break; // { dg-error "'__builtin_within_lifetime' called with a null
> pointer" }
> + case 3: __builtin_is_within_lifetime (&foo); break; // {
> dg-error "'__builtin_within_lifetime' called with pointer to function" }
> + case 4: __builtin_is_within_lifetime (&v); break; // { dg-error
> "'__builtin_within_lifetime' on 'v' from outside current evaluation is not
> a constant expression" }
> + }
> + return true;
> +}
> +
> +static_assert (bar (0));
> +bool c = bar (1); // { dg-error "is not a
> constant expression" }
> +bool d = bar (2); // { dg-error "is not a
> constant expression" }
> +bool e = bar (3); // { dg-error "is not a
> constant expression" }
> +bool f = bar (4); // { dg-error "is not a
> constant expression" }
> +
> +constexpr struct { union { int a; short b; }; mutable int c; } g = { .b =
> 42 };
> +constexpr int s = 42;
> +constexpr int t[2] = {};
> +
> +consteval bool
> +qux (int x)
> +{
> + if (!__builtin_is_within_lifetime (&g))
> + return false;
> + if (__builtin_is_within_lifetime (&g.a))
> + return false;
> + if (!__builtin_is_within_lifetime (&g.b))
> + return false;
> + if (x == 1)
> + __builtin_is_within_lifetime (&g.c); // { dg-error
> "'__builtin_within_lifetime' on 'mutable' sub-object '<unnamed struct>::c'"
> }
> + else if (x == 2)
> + __builtin_is_within_lifetime (&s + 1); // { dg-error
> "'__builtin_within_lifetime' on '\\\*\\\(\\\(\\\& s\\\) \\\+ 4\\\)' from
> outside current evaluation is not a constant expression" }
> + else if (x == 3)
> + __builtin_is_within_lifetime (t + 2); // { dg-error "array
> subscript value '2' is outside the bounds of array 't' of type 'const int
> \\\[2\\\]'" }
> + return true;
> +}
> +
> +static_assert (qux (0));
> +bool h = qux (1); // { dg-error "is not a
> constant expression" }
> +bool i = qux (2); // { dg-error "is not a
> constant expression" }
> +bool j = qux (3); // { dg-error "is not a
> constant expression" }
> +
> +struct A {
> + constexpr A () {}
> + constexpr A (const A &) {}
> + constexpr ~A () {}
> +};
> +
> +template <typename T>
> +constexpr T &
> +corge (T &&x)
> +{
> + return static_cast <T &> (x);
> +}
> +
> +consteval bool
> +fred ()
> +{
> + static_assert (&corge (0));
> + static_assert (&corge (A {}));
> + if (!__builtin_is_within_lifetime (&corge (0)))
>
Interesting cases, the lifetime of temporary ends with the full expression.
so it should return true.
> + return false;
> + if (!__builtin_is_within_lifetime (&corge (A {})))
> + return false;
> + return true;
> +}
> +
> +static_assert (fred ());
> +
> +constexpr const int &k = 0;
> +static_assert (__builtin_is_within_lifetime (&k));
> +
> +template <typename T>
> +consteval T *
> +waldo ()
> +{
> + T t;
> + return &t; // { dg-warning "address of local variable 't'
> returned" }
> +
> +}
> +
> +constexpr bool l = __builtin_is_within_lifetime (waldo <int> ()); //
> { dg-error "'__builtin_within_lifetime' on 't' outside its lifetime is not
> a constant expression" }
> +constexpr bool m = __builtin_is_within_lifetime (waldo <int [2]> ()); //
> { dg-error "'__builtin_within_lifetime' on 't' outside its lifetime is not
> a constant expression" }
> +
> +template <typename T, T V>
> +struct integral_constant
> +{
> + static constexpr T value = V;
> + using value_type = T;
> + using type = integral_constant <T, V>;
> + constexpr operator value_type () const noexcept { return value; }
> + constexpr value_type operator () () const noexcept { return value; }
> +};
> +
> +template <bool V>
> +using bool_constant = integral_constant <bool, V>;
> +
> +using true_type = bool_constant <true>;
> +using false_type = bool_constant <false>;
> +
> +constexpr int n = 42;
> +
> +template <auto T>
> +concept B = bool_constant <__builtin_is_within_lifetime (T ())>::value;
> +
> +static_assert (B <[] { return &n; }>);
> +static_assert (!B <[] { return static_cast <int *> (nullptr); }>);
> +static_assert (!B <[] { return static_cast <void (*) (int *)> (&foo); }>);
> +
> +template <auto T>
> +constexpr true_type
> +garply () requires B <T>
> +{
> + return {};
> +}
> +
> +template <auto T>
> +false_type
> +garply ()
> +{
> + return {};
> +}
> +
> +static_assert (decltype (garply <[] { return &n; }> ())::value);
> +static_assert (!decltype (garply <[] { return static_cast <int *>
> (nullptr); }> ())::value);
> +true_type (*o) () = &garply <[] { return &n; }>;
> --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime5.C 2026-08-25
> 17:08:36.082281771 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime5.C 2026-08-25
> 17:21:49.881858232 +0200
> @@ -0,0 +1,46 @@
> +// P3450R1 - Extend std::is_within_lifetime
> +// { dg-do compile { target c++23 } }
> +// { dg-skip-if "sizeof (bool) > 1" { powerpc-*-darwin* } }
> +
> +namespace std {
> + template <class T>
> + consteval bool
> + is_within_lifetime (const T *p) noexcept
> + {
> + return __builtin_is_within_lifetime (p);
> + }
> +}
> +
> +struct OptBool {
> + union { bool b; char c; };
> +
> + // note: this assumes common implementation properties for bool and
> char:
> + // * sizeof (bool) == sizeof (char), and
> + // * the value representations for true and false are distinct
> + // from the value representation for 2
> + constexpr OptBool () : c (2) { }
> + constexpr OptBool (bool b) : b (b) { }
> +
> + constexpr bool has_value () const
> + {
> + if consteval
> + {
> + return std::is_within_lifetime (&b); // during constant
> evaluation, cannot read from c
> + }
> + else
> + {
> + return c != 2; // during runtime, must
> read from c
> + }
> + }
> +
> + constexpr const bool &operator * () const { return b; }
> +};
> +
> +constexpr OptBool disengaged;
> +constexpr OptBool engaged (true);
> +static_assert (!disengaged.has_value ());
> +static_assert (engaged.has_value ());
> +static_assert (*engaged);
> +constexpr OptBool engaged2 (false);
> +static_assert (engaged2.has_value ());
> +static_assert (!*engaged2);
> --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime6.C 2026-08-25
> 17:54:50.544931689 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime6.C 2026-08-25
> 17:55:34.156362767 +0200
> @@ -0,0 +1,41 @@
> +// P3450R1 - Extend std::is_within_lifetime
> +// { dg-do compile { target c++26 } }
> +// { dg-skip-if "sizeof (bool) > 1" { powerpc-*-darwin* } }
> +
> +#include <type_traits>
> +
> +#if __cpp_lib_within_lifetime >= 202306L
> +struct OptBool {
> + union { bool b; char c; };
> +
> + // note: this assumes common implementation properties for bool and
> char:
> + // * sizeof (bool) == sizeof (char), and
> + // * the value representations for true and false are distinct
> + // from the value representation for 2
> + constexpr OptBool () : c (2) { }
> + constexpr OptBool (bool b) : b (b) { }
> +
> + constexpr bool has_value () const
> + {
> + if consteval
> + {
> + return std::is_within_lifetime (&b); // during constant
> evaluation, cannot read from c
> + }
> + else
> + {
> + return c != 2; // during runtime, must
> read from c
> + }
> + }
> +
> + constexpr const bool &operator * () const { return b; }
> +};
> +#endif
> +
> +constexpr OptBool disengaged;
> +constexpr OptBool engaged (true);
> +static_assert (!disengaged.has_value ());
> +static_assert (engaged.has_value ());
> +static_assert (*engaged);
> +constexpr OptBool engaged2 (false);
> +static_assert (engaged2.has_value ());
> +static_assert (!*engaged2);
> --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime7.C 2026-08-25
> 19:13:45.210104915 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime7.C 2026-08-25
> 19:28:32.714466636 +0200
> @@ -0,0 +1,75 @@
> +// P3450R1 - Extend std::is_within_lifetime
> +// { dg-do compile { target c++20 } }
> +// { dg-options "" }
> +
> +#include "../cpp2a/construct_at.h"
> +
> +consteval bool
> +foo (int n)
> +{
> + int a[n];
> + if (!__builtin_is_within_lifetime (&a))
> + return false;
> + if (!__builtin_is_within_lifetime (&a[0]))
> + return false;
> + if (!__builtin_is_within_lifetime (&a[n / 2]))
> + return false;
> + if (!__builtin_is_within_lifetime (&a[n - 1]))
> + return false;
> + std::destroy_at (&a[0]);
> + if (!__builtin_is_within_lifetime (&a))
> + return false;
> + if (__builtin_is_within_lifetime (&a[0]))
> + return false;
> + if (!__builtin_is_within_lifetime (&a[n / 2]))
> + return false;
> + if (!__builtin_is_within_lifetime (&a[n - 1]))
> + return false;
> + std::construct_at (&a[0]);
> + std::destroy_at (&a[n / 2]);
> + if (!__builtin_is_within_lifetime (&a))
> + return false;
> + if (!__builtin_is_within_lifetime (&a[0]))
> + return false;
> + if (__builtin_is_within_lifetime (&a[n / 2]))
> + return false;
> + if (!__builtin_is_within_lifetime (&a[n - 1]))
> + return false;
> + std::construct_at (&a[n / 2]);
> + std::destroy_at (&a[n - 1]);
> + if (!__builtin_is_within_lifetime (&a))
> + return false;
> + if (!__builtin_is_within_lifetime (&a[0]))
> + return false;
> + if (!__builtin_is_within_lifetime (&a[n / 2]))
> + return false;
> + if (__builtin_is_within_lifetime (&a[n - 1]))
> + return false;
> + std::construct_at (&a[n - 1]);
> + _Complex double b = 1.0;
> + if (!__builtin_is_within_lifetime (&b))
> + return false;
> + if (!__builtin_is_within_lifetime (&__real__ b))
> + return false;
> + if (!__builtin_is_within_lifetime (&__imag__ b))
> + return false;
> + std::destroy_at (&__real__ b);
> + if (!__builtin_is_within_lifetime (&b))
> + return false;
> + if (__builtin_is_within_lifetime (&__real__ b))
> + return false;
> + if (!__builtin_is_within_lifetime (&__imag__ b))
> + return false;
> + std::construct_at (&__real__ b);
> + std::destroy_at (&__imag__ b);
> + if (!__builtin_is_within_lifetime (&b))
> + return false;
> + if (!__builtin_is_within_lifetime (&__real__ b))
> + return false;
> + if (__builtin_is_within_lifetime (&__imag__ b))
> + return false;
> + std::construct_at (&__imag__ b);
> + return true;
> +}
> +
> +static_assert (foo (42));
> --- a/libstdc++-v3/include/bits/version.def 2026-08-20
> 08:39:02.424319866 +0200
> +++ b/libstdc++-v3/include/bits/version.def 2026-08-25
> 13:23:45.637499510 +0200
> @@ -2497,6 +2497,15 @@ ftms = {
> };
>
> ftms = {
> + name = within_lifetime;
> + values = {
> + v = 202306;
> + cxxmin = 26;
> + extra_cond = "__has_builtin(__builtin_is_within_lifetime)";
> + };
> +};
> +
> +ftms = {
> name = hardened_array;
> values = {
> v = 202502;
> --- a/libstdc++-v3/include/bits/version.h 2026-08-20
> 08:39:02.424319866 +0200
> +++ b/libstdc++-v3/include/bits/version.h 2026-08-25
> 13:23:50.821020242 +0200
> @@ -2749,6 +2749,16 @@
> #endif /* !defined(__cpp_lib_valarray) */
> #undef __glibcxx_want_valarray
>
> +#if !defined(__cpp_lib_within_lifetime)
> +# if (__cplusplus > 202302L) &&
> (__has_builtin(__builtin_is_within_lifetime))
> +# define __glibcxx_within_lifetime 202306L
> +# if defined(__glibcxx_want_all) ||
> defined(__glibcxx_want_within_lifetime)
> +# define __cpp_lib_within_lifetime 202306L
> +# endif
> +# endif
> +#endif /* !defined(__cpp_lib_within_lifetime) */
> +#undef __glibcxx_want_within_lifetime
> +
> #if !defined(__cpp_lib_hardened_array)
> # if (__cplusplus >= 201103L) && (defined(_GLIBCXX_ASSERTIONS))
> # define __glibcxx_hardened_array 202502L
> --- a/libstdc++-v3/include/std/type_traits 2026-07-24
> 18:38:54.338025248 +0200
> +++ b/libstdc++-v3/include/std/type_traits 2026-08-25
> 13:30:00.826519412 +0200
> @@ -66,6 +66,7 @@
> #define __glibcxx_want_type_trait_variable_templates
> #define __glibcxx_want_unwrap_ref
> #define __glibcxx_want_void_t
> +#define __glibcxx_want_within_lifetime
> #include <bits/version.h>
>
> extern "C++"
> @@ -4400,6 +4401,13 @@ template<typename _Ret, typename _Fn, ty
> };
> #endif // C++11
>
> +#if __cpp_lib_within_lifetime >= 202306L // C++ >= 26
> + template<typename _Tp>
> + consteval bool
> + is_within_lifetime(const _Tp* __p) noexcept
> + { return __builtin_is_within_lifetime (__p); }
> +#endif
> +
> /// @} group metaprogramming
>
> _GLIBCXX_END_NAMESPACE_VERSION
> --- a/libstdc++-v3/src/c++23/std.cc.in 2026-08-17 10:00:21.952853341
> +0200
> +++ b/libstdc++-v3/src/c++23/std.cc.in 2026-08-25 13:33:02.423108969
> +0200
> @@ -3610,6 +3610,9 @@ export namespace std
> using std::is_structural;
> using std::is_structural_v;
> #endif
> +#if __cpp_lib_within_lifetime >= 202306L
> + using std::is_within_lifetime;
> +#endif
> }
>
> // <typeindex>
>
> Jakub
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260826/54e7bd11/attachment-0001.htm>
More information about the Libstdc++
mailing list