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

Jason Merrill jason@redhat.com
Wed Aug 26 23:06:21 GMT 2026


On 8/26/26 4:08 AM, Jakub Jelinek 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).

prooovides

> 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.

Hmm, silently returning false at runtime seems like a lie.

> 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

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.

> 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.

> 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

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

> +/* 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?

> +		  "__builtin_within_lifetime");

Missing "is" in a bunch of places.

> +	      error_at (loc, "%qs on allocated storage after deallocation "
> +			     "is not a constant expression",

This won't survive re-indentation without added parens.

> +			"__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;

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.

> +  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.

> +      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;

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.

> +	if (TREE_CODE (val) != CONSTRUCTOR)
> +	  return boolean_false_node;

This seems like an assertion rather than a return false?

Jason



More information about the Libstdc++ mailing list