[PATCH] c++, libstdc++, v2: Implement C++26 P2641R4 - Checking if a union alternative is active

Jakub Jelinek jakub@redhat.com
Thu Aug 27 13:31:27 GMT 2026


On Wed, Aug 26, 2026 at 04:06:21PM -0700, Jason Merrill wrote:
> On 8/26/26 4:08 AM, Jakub Jelinek wrote:
> > The following patch attempts to implement this paper (though for now just
> > using what the FE prooovides instead of introducing new stuff, see below).
> 
> prooovides

Sorry, my keyboard is dying, will replace it during the weekend.

> > 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.
> 
> Hmm, silently returning false at runtime seems like a lie.

Ok, changed the patch so that __builtin_is_within_lifetime is a consteval
function and adjusted testcase accordingly (for uses from within
std::is_within_lifetime obviously it changed nothing, but for direct calls
in some spots yes).

> > 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
> 
> Hmm, is it possible to have an array CONSTRUCTOR with
> CONSTRUCTOR_NO_CLEARING which doesn't represent that situation?  If the
> array is out of lifetime it shouldn't have a CONSTRUCTOR.  There isn't the
> same period of construction issue that there is with classes.

Consider
consteval bool
foo ()
{
  int a[4];
  if (!__builtin_is_within_lifetime (&a[2]))
    return false;
  a[1] = 42;
  if (!__builtin_is_within_lifetime (&a[2]))
    return false;
  return true;
}

static_assert (foo ());

Already on the first call to the builtin a has *valp of CONSTRUCTOR
with the ARRAY_TYPE with no elements and CONSTRUCTOR_NO_CLEARING, and it
means the whole array including all elements is within lifetime, rather than
that the array is and no children are.  This is now also in
within-lifetime1.C.
So I don't see how we can avoid a separate bit for it.

> > 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).
> 
> Yes, I think that's UB->non-constant because the pointer no longer "points
> to an object" after the storage is gone.

Thinking about this more (but nothing in the patch yet), I think we have a
problem that void_node for complete objects can mean two different things.
It can mean we've deallocated storage for it, or it can mean we've called
std::destroy_at.  The former should result in an error if not quiet and
be *non_constant_p, while the latter should just mean the builtin returns
false.  Now, if it is just a subobject, void_node can mean just one thing,
that it is not in lifetime but still has storage allocated.
Say:

consteval bool
foo (bool x)
{
  int *p;
  {
    int a;
    p = &a;
    if (!__builtin_is_within_lifetime (&a))
      return false;
    std::destroy_at (&a);
    if (__builtin_is_within_lifetime (&a))
      return false;
    std::construct_at (&a);
    if (!__builtin_is_within_lifetime (&a))
      return false;
  }
  if (x)
    __builtin_is_within_lifetime (p);
  return true;
}

static_assert (foo (false));
bool a = foo (true);

This fails incorrectly IMHO with the current patch, already the
second builtin call results in it being non-constant.

Shall we remove the hash_map record on deallocation instead or store
something magic other than void_node?

> As you've seen, I'm arguing in CWG that this specification should be changed
> to "started initialization".

I've adjusted the testsuite assuming that is changed (so removed the xfails
for it).  On the other side I've added the testcase I've posted in the
patch description rather than in the patch and xfailed that.

> > +/* 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)
> 
> These checks seem like they belong at semantic analysis time, rather than
> later during evaluation or gimplification?

Ok, moved to finish_call_expr instead (for normal builtins, such checks are
usually either inn resolve_overloaded_builtin or in say builtins.cc folding
of the builtin, but resolve_overloaded_builtin isn't called for FE
builtins).

> > +		  "__builtin_within_lifetime");
> 
> Missing "is" in a bunch of places.

Fixed.

> > +	      error_at (loc, "%qs on allocated storage after deallocation "
> > +			     "is not a constant expression",
> 
> This won't survive re-indentation without added parens.

Fixed.

> > +  /* 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;
> 
> Hmm, it seems fragile to use NULL_TREE as a magic value below when it
> generally means already means something different (i.e. vacuous
> initialization), which is why you replace it here.

So do you want instead 2 magic values, one for zero initialization
implicit stuff and one for uninitialized implicit stuff?
Or it can be a separate flag or flags from the val variable.
Say bool is_zero_init = false, is_unitialized = false;

> > +  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;
> 
> So this will return true if val is null or void_node or other
> non-COMPLEX_EXPR?  That seems wrong.

If val is NULL, then it is the zero initialization case and both
real and imag parts are within lifetime.
If val is void_list_node, then it is the uninitialized case and
again both are within lifetime.
If val is void_node, then this shouldn't have been reached, we should
have returned boolean_false_node already in previous iteration (if any)
or if toplevel, right now reject as non-constant (see above for why
that isn't correct).
I think val perhaps could be in some cases CONSTRUCTOR with
CONSTRUCTOR_NO_PADDING (for uninitialized value of the whole _Complex),
but there are no FIELD_DECLs/indexes for _Complex so I think otherwise
it should be only COMPLEX_CST or COMPLEX_EXPR (and COMPLEX_EXPR with
void_node first and/or second argument stands clearly (as seen in the
testcase) for destroy_at part).

> > +	if (val == NULL_TREE || val == void_list_node)
> > +	  continue;
> 
> So if val is void_list_node and there are no unions involved, we'll iterate
> through the remaining refs and then return true below?
> 
> It's not clear to me why it's ever useful to set val to void_list_node over
> immediately returning false; if a containing object is uninitialized, all
> subobjects are as well.

They are uninitialized, but within lifetime (at least if they have vacuous
initialization).  So, I think we want to return true, but need to iterate
over the remaining refs just in case there is a union member access (then
we don't want to return true but false).

> > +	if (TREE_CODE (val) != CONSTRUCTOR)
> > +	  return boolean_false_node;
> 
> This seems like an assertion rather than a return false?

Wonder about error_mark_node or something similar, but perhaps I could make
it into assert, yes (not done in the following patch yet).

2026-08-27  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.
	* 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.
	* semantics.cc (finish_call_expr): Perform semantic checking of
	__builtin_is_within_lifetime.
	* 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-27 13:46:12.727137367 +0200
+++ b/gcc/cp/cp-tree.h	2026-08-27 14:01:10.801276615 +0200
@@ -7211,6 +7211,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
 };
 
--- a/gcc/cp/tree.cc	2026-08-27 13:46:12.731137314 +0200
+++ b/gcc/cp/tree.cc	2026-08-27 14:15:45.906780476 +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-27 13:46:12.729137341 +0200
+++ b/gcc/cp/decl.cc	2026-08-27 14:15:45.909780438 +0200
@@ -5673,6 +5673,12 @@ 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);
+  SET_DECL_IMMEDIATE_FUNCTION_P (decl);
+
   integer_two_node = build_int_cst (NULL_TREE, 2);
 
   /* Guess at the initial static decls size.  */
--- a/gcc/cp/semantics.cc	2026-08-27 13:46:12.731137314 +0200
+++ b/gcc/cp/semantics.cc	2026-08-27 14:15:45.915780359 +0200
@@ -3540,6 +3540,32 @@ finish_call_expr (tree fn, vec<tree, va_
 	      warn_for_memset (input_location, arg0, arg2, literal_mask);
 	    }
 
+	  if (TREE_CODE (fn) == FUNCTION_DECL
+	      && fndecl_built_in_p (fn, CP_BUILT_IN_IS_WITHIN_LIFETIME,
+				    BUILT_IN_FRONTEND))
+	    {
+	      /* Unless users call the builtin directly, the following 2 checks
+		 should be ensured from std::is_within_lifetime template.  */
+	      if (vec_safe_length (*args) != 1)
+		{
+		  if (complain & tf_error)
+		    error ("%<__builtin_is_within_lifetime%> needs a single "
+			   "argument");
+		  return error_mark_node;
+		}
+	      tree arg = (**args)[0];
+	      if (error_operand_p (arg))
+		return error_mark_node;
+	      tree ptype = TREE_TYPE (arg);
+	      if (!POINTER_TYPE_P (ptype))
+		{
+		  if (complain & tf_error)
+		    error ("%<__builtin_is_within_lifetime%> argument type "
+			   "%qT is not pointer type", ptype);
+		  return error_mark_node;
+		}
+	    }
+
 	  /* A call to a namespace-scope function.  */
 	  result = build_new_function_call (fn, args, orig_complain);
 	}
--- a/gcc/cp/constexpr.cc	2026-08-27 13:46:12.726137380 +0200
+++ b/gcc/cp/constexpr.cc	2026-08-27 14:43:57.050845241 +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,248 @@ 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);
+  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_is_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_is_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_is_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_is_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_is_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_is_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 +2855,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-27 13:46:24.234064648 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime1.C	2026-08-27 13:46:24.234064648 +0200
@@ -0,0 +1,353 @@
+// 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 __LINE__;
+  if (!std::is_within_lifetime (&b))
+    return __LINE__;
+  if (!std::is_within_lifetime (&b.b))
+    return __LINE__;
+  if (!std::is_within_lifetime (&c))
+    return __LINE__;
+  if (!std::is_within_lifetime (&c.b))
+    return __LINE__;
+  if (!std::is_within_lifetime (&c.c))
+    return __LINE__;
+  if (!std::is_within_lifetime (&c.c[1]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&c.f))
+    return __LINE__;
+  if (!std::is_within_lifetime (&c.f.d))
+    return __LINE__;
+  if (std::is_within_lifetime (&c.f.e))
+    return __LINE__;
+  if (!std::is_within_lifetime (&d))
+    return __LINE__;
+  if (!std::is_within_lifetime (&e))
+    return __LINE__;
+  if (!std::is_within_lifetime (&e.a))
+    return __LINE__;
+  if (!std::is_within_lifetime (&e.g))
+    return __LINE__;
+  if (!std::is_within_lifetime (&e.g.b))
+    return __LINE__;
+  if (std::is_within_lifetime (&e.g.c))
+    return __LINE__;
+  if (std::is_within_lifetime (&e.g.d))
+    return __LINE__;
+  if (std::is_within_lifetime (&e.g.f))
+    return __LINE__;
+  if (!std::is_within_lifetime (&f))
+    return __LINE__;
+  if (!std::is_within_lifetime (&f.a))
+    return __LINE__;
+  if (!std::is_within_lifetime (&f.g))
+    return __LINE__;
+  if (std::is_within_lifetime (&f.g.b))
+    return __LINE__;
+  if (std::is_within_lifetime (&f.g.c))
+    return __LINE__;
+  if (std::is_within_lifetime (&f.g.d))
+    return __LINE__;
+  if (!std::is_within_lifetime (&f.g.f))
+    return __LINE__;
+  if (!std::is_within_lifetime (&f.g.f[0]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&f.g.f[0].e))
+    return __LINE__;
+  if (!std::is_within_lifetime (&f.g.f[1].e))
+    return __LINE__;
+  if (!std::is_within_lifetime (&g))
+    return __LINE__;
+  if (!std::is_within_lifetime (&g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&g.f))
+    return __LINE__;
+  if (!std::is_within_lifetime (&g.e.a))
+    return __LINE__;
+  if (std::is_within_lifetime (&g.e.d))
+    return __LINE__;
+  g.e.d.c = 1;
+  if (!std::is_within_lifetime (&g))
+    return __LINE__;
+  if (!std::is_within_lifetime (&g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&g.f))
+    return __LINE__;
+  if (std::is_within_lifetime (&g.e.a))
+    return __LINE__;
+  if (!std::is_within_lifetime (&g.e.d))
+    return __LINE__;
+  if (std::is_within_lifetime (&g.e.d.b))
+    return __LINE__;
+  if (!std::is_within_lifetime (&g.e.d.c))
+    return __LINE__;
+  g.f = 42;
+  if (!std::is_within_lifetime (&g))
+    return __LINE__;
+  if (std::is_within_lifetime (&g.e))
+    return __LINE__;
+  if (!std::is_within_lifetime (&g.f))
+    return __LINE__;
+  if (!std::is_within_lifetime (&h))
+    return __LINE__;
+  if (std::is_within_lifetime (&h.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&h.f))
+    return __LINE__;
+  if (!std::is_within_lifetime (&i))
+    return __LINE__;
+  if (!std::is_within_lifetime (&i.b))
+    return __LINE__;
+  if (!std::is_within_lifetime (&i.c[1]))
+    return __LINE__;
+  if (!std::is_within_lifetime (&i.f))
+    return __LINE__;
+  if (std::is_within_lifetime (&i.f.d))
+    return __LINE__;
+  if (std::is_within_lifetime (&i.f.e))
+    return __LINE__;
+  if (!std::is_within_lifetime (&j))
+    return __LINE__;
+  if (!std::is_within_lifetime (&k))
+    return __LINE__;
+  unsigned char l = std::bit_cast <unsigned char> (k);
+  if (!std::is_within_lifetime (&l))
+    return __LINE__;
+  struct {} m;
+  if (!std::is_within_lifetime (&m))
+    return __LINE__;
+  unsigned char n = std::bit_cast <unsigned char> (m);
+  if (!std::is_within_lifetime (&n))
+    return __LINE__;
+  int o;
+  if (!std::is_within_lifetime (static_cast <volatile int *> (&o)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&o)))
+    return __LINE__;
+  volatile int p;
+  if (!std::is_within_lifetime (const_cast <int *> (&p)))
+    return __LINE__;
+  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 __LINE__;
+  if (!std::is_within_lifetime (&q.g))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.h))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.f))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.c))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.d))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.c.a))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.c.b))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.g)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.h)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.f)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.d)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b)))
+    return __LINE__;
+  q.g.f = 0;
+  if (!std::is_within_lifetime (&q))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.g))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.h))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.g.f))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.c))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.d))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.c.a))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.c.b))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.g)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.h)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.g.f)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.d)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b)))
+    return __LINE__;
+  q.g.e.d = 0;
+  if (!std::is_within_lifetime (&q))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.g))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.h))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.f))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.c))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.g.e.d))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.c.a))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.c.b))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.g)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.h)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.g.e)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.f)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.d)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b)))
+    return __LINE__;
+  q.g.e.c.a = 0;
+  if (!std::is_within_lifetime (&q))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.g))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.h))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.g.e))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.f))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.g.e.c))
+    return __LINE__;
+  if (std::is_within_lifetime (&q.g.e.d))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.g.e.c.a))
+    return __LINE__;
+  if (!std::is_within_lifetime (&q.g.e.c.b))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.g)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.h)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.g.e)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.f)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.c)))
+    return __LINE__;
+  if (std::is_within_lifetime (static_cast <void *> (&q.g.e.d)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b)))
+    return __LINE__;
+  struct { union { int a; short b; }; mutable int c; } r = { .b = 42 };
+  if (!std::is_within_lifetime (&r))
+    return __LINE__;
+  if (std::is_within_lifetime (&r.a))
+    return __LINE__;
+  if (!std::is_within_lifetime (&r.b))
+    return __LINE__;
+  if (!std::is_within_lifetime (&r.c))
+    return __LINE__;
+  if (!std::is_within_lifetime (&s))
+    return __LINE__;
+  if (!std::is_within_lifetime (const_cast <int *> (&s)))
+    return __LINE__;
+  if (!std::is_within_lifetime (const_cast <volatile int *> (&s)))
+    return __LINE__;
+  if (!std::is_within_lifetime (static_cast <const void *> (&s)))
+    return __LINE__;
+  if (!std::is_within_lifetime (t))
+    return __LINE__;
+  if (!std::is_within_lifetime (t + 0))
+    return __LINE__;
+  if (!std::is_within_lifetime (t + 1))
+    return __LINE__;
+  int u[4];
+  if (!std::is_within_lifetime (&u))
+    return __LINE__;
+  if (!std::is_within_lifetime (&u[2]))
+    return __LINE__;
+  u[1] = 42;
+  if (!std::is_within_lifetime (&u))
+    return __LINE__;
+  if (!std::is_within_lifetime (&u[2]))
+    return __LINE__;
+  return 0;
+}
+
+static_assert (foo () == 0);
--- a/gcc/testsuite/g++.dg/cpp26/within-lifetime2.C	2026-08-27 13:46:24.234179319 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime2.C	2026-08-27 13:46:24.234179319 +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-27 13:46:24.234284309 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C	2026-08-27 14:36:59.941246962 +0200
@@ -0,0 +1,220 @@
+// 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_is_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_is_within_lifetime' on 'c' outside its lifetime is not a constant expression" }
+  }
+  if (x == 2)
+    __builtin_is_within_lifetime (a);	// { dg-error "'__builtin_is_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_is_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_is_within_lifetime' on 'd' outside its lifetime is not a constant expression" }
+  }
+  if (x == 5)
+    __builtin_is_within_lifetime (b);	// { dg-error "'__builtin_is_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_is_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_is_within_lifetime' on 'c' outside its lifetime is not a constant expression" }
+  }
+  if (x == 8)
+    __builtin_is_within_lifetime (a);	// { dg-error "'__builtin_is_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_is_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_is_within_lifetime' on 'd' outside its lifetime is not a constant expression" }
+  }
+  if (x == 11)
+    __builtin_is_within_lifetime (b);	// { dg-error "'__builtin_is_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_is_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); };
+constexpr A n;
+
+struct B { consteval B () : a (42), b (std::is_within_lifetime (&a)) {} int a; bool b; };
+constexpr B o;
+
+struct C { consteval C () { __builtin_is_within_lifetime (this); } } p;
+
+struct D {
+  consteval D () : a (0), b (0), c (0) {}
+  consteval D (int x, int y, int z)
+  : a (x), b (y + std::is_within_lifetime (&this->a) * 32
+		+ std::is_within_lifetime (&this->b) * 64
+		+ std::is_within_lifetime (&this->c) * 128), c (z) {}
+  constexpr ~D () {}
+  int a, b, c;
+};
+
+consteval int
+foo ()
+{
+  D 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;
+}
+
+// FIXME: In mem-initializer of b, a should be already constructed,
+// so within lifetime, but b is in the middle of construction and c
+// construction has not started yet.
+static_assert (foo () == 4 + 5 + 6 + 32);	// { dg-bogus "note: the comparison reduces to '\\\(239 == 47\\\)'" "" { xfail *-*-* } }
+						// { dg-bogus "static assertion failed" "" { xfail *-*-* } .-1 }
--- a/gcc/testsuite/g++.dg/cpp26/within-lifetime4.C	2026-08-27 13:46:24.234423781 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime4.C	2026-08-27 14:30:29.436243655 +0200
@@ -0,0 +1,157 @@
+// 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_is_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);	// { dg-error "'__builtin_is_within_lifetime' argument type 'std::nullptr_t' is not pointer type" }
+  __builtin_is_within_lifetime (static_cast <int *> (nullptr));	// { dg-error "is not a constant expression" }
+						// { dg-error "'__builtin_is_within_lifetime' called with a null pointer" "" { target *-*-* } .-1 }
+  __builtin_is_within_lifetime (&foo);		// { dg-error "is not a constant expression" }
+						// { dg-error "'__builtin_is_within_lifetime' called with pointer to function" "" { target *-*-* } .-1 }
+  __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);		// { dg-error "call to consteval function '__builtin_is_within_lifetime\\\(p\\\)' is not a constant expression" }
+						// { dg-error "'p' is not a constant expression" "" { target *-*-* } .-1 }
+}
+
+extern int v;
+
+consteval bool
+bar (int x)
+{
+  switch (x)
+    {
+    case 0: __builtin_is_within_lifetime (&b); break;
+    case 1: __builtin_is_within_lifetime (static_cast <int *> (nullptr)); break; // { dg-error "'__builtin_is_within_lifetime' called with a null pointer" }
+    case 2: __builtin_is_within_lifetime (&foo); break;	// { dg-error "'__builtin_is_within_lifetime' called with pointer to function" }
+    case 3: __builtin_is_within_lifetime (&v); break; // { dg-error "'__builtin_is_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" }
+
+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_is_within_lifetime' on 'mutable' sub-object '<unnamed struct>::c'" }
+  else if (x == 2)
+    __builtin_is_within_lifetime (&s + 1);	// { dg-error "'__builtin_is_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 (__builtin_is_within_lifetime (&corge (0)));
+  static_assert (__builtin_is_within_lifetime (&corge (A {})));
+  if (!__builtin_is_within_lifetime (&corge (0)))
+    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_is_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_is_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-27 13:46:24.234539263 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime5.C	2026-08-27 13:46:24.234539263 +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-27 13:46:24.234643741 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime6.C	2026-08-27 13:46:24.234643741 +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-27 13:46:24.234732513 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime7.C	2026-08-27 13:46:24.234732513 +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



More information about the Libstdc++ mailing list