[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