[PATCH] c++, libstdc++, v3: Implement C++26 P3726R2 - Adjustments to Union Lifetime Rules [PR125845]

Tomasz Kaminski tkaminsk@redhat.com
Wed Sep 9 10:06:14 GMT 2026


On Wed, Sep 9, 2026 at 11:27 AM Jakub Jelinek <jakub@redhat.com> wrote:

> On Wed, Sep 09, 2026 at 08:42:32AM +0200, Tomasz Kaminski wrote:
> > I would much more preffer the start_lifetime(a.x) to be non-constant
> > expressions
> > if `a` (or any containing object) is not within its lifetime.
> >
> > I believe this function should start a lifetime of at most a single
> object.
> > Otherwise
> > it would be unclear, if the enclosing objects should be created (with all
> > members)
> > or only have their lifetimes started. This becomes even more complicated
> if
> > we
> > consider non-trivial enclosing objects.
> >
> > Also, I want to avoid orphaned in-lifetime objects, when we have some
> > subobject
> > in lifetime, while some of the containing objects are not. This is why
> the
> > paper hasn't
> > proposed end_lifetime function, as it would naturally lead to such
> > situations, and
> > instead relies on storage reuse to end lieftime of any objects that have
> > they lifetime started.
>
> Here is an incremental patch on top of the patch posted today, which
> essentially performs a __builtin_is_within_lifetime call on the containing
> (sub)object if any before trying to evaluate the store.
>
> --- a/gcc/cp/constexpr.cc       2026-09-08 22:26:48.000000000 +0200
> +++ b/gcc/cp/constexpr.cc       2026-09-09 10:47:26.825464921 +0200
> @@ -2541,62 +2541,13 @@ cxx_eval_constexpr_diag (const constexpr
>  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.  */
> +/* Helper function for cxx_eval_is_within_lifetime and
> +   cxx_eval_start_lifetime.  Check if ARG 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)
> +is_within_lifetime (location_t loc, const constexpr_ctx *ctx, tree t,
> tree arg,
> +                   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))
> @@ -2793,6 +2744,66 @@ cxx_eval_is_within_lifetime (const const
>    return boolean_true_node;
>  }
>
> +/* 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;
> +  return is_within_lifetime (loc, ctx, t, arg, non_constant_p, overflow_p,
> +                            jump_target);
> +}
> +
>  static tree cxx_eval_store_expression (const constexpr_ctx *, tree,
>                                        value_cat, bool *, bool *,
>                                        tree *);
> @@ -2805,7 +2816,14 @@ cxx_eval_start_lifetime (const constexpr
>                          bool *overflow_p, tree *jump_target)
>  {
>    location_t loc = EXPR_LOCATION (t);
> -  tree arg = CALL_EXPR_ARG (t, 0);
> +  tree arg = CALL_EXPR_ARG (t, 0), within_lifetime;
> +  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;
>    arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg);
>    arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue,
>                                       non_constant_p, overflow_p,
> @@ -2814,6 +2832,31 @@ cxx_eval_start_lifetime (const constexpr
>      return NULL_TREE;
>    if (*non_constant_p)
>      return t;
> +  switch (TREE_CODE (arg))
> +    {
> +    case COMPONENT_REF:
> +    case ARRAY_REF:
> +    case REALPART_EXPR:
> +    case IMAGPART_EXPR:
> +      within_lifetime
> +       = is_within_lifetime (loc, ctx, t, TREE_OPERAND (arg, 0),
> +                             non_constant_p, overflow_p, jump_target);
> +      if (*jump_target)
> +       return NULL_TREE;
> +      if (*non_constant_p)
> +       return t;
> +      if (within_lifetime != boolean_true_node)
> +       {
> +         if (!ctx->quiet)
> +           error_at (loc, "%qs with containing object not within
> lifetime",
> +                     "__builtin_is_within_lifetime");
> +         *non_constant_p = true;
> +         return t;
> +       }
> +      break;
> +    default:
> +      break;
> +    }
>    tree ctor = build_constructor (TREE_TYPE (arg), NULL);
>    CONSTRUCTOR_NO_CLEARING (ctor) = 1;
>    CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (ctor) = 1;
> --- a/gcc/testsuite/g++.dg/cpp26/trivial-union6.C       2026-09-08
> 22:19:17.810884355 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union6.C       2026-09-09
> 11:15:08.446464272 +0200
> @@ -76,12 +76,92 @@ constexpr E
>  fE3 (bool x)
>  {
>    E a;
> -  __builtin_start_lifetime (&a.a.b[0]);
> +  __builtin_start_lifetime (&a.a.b[0]);        // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
>    if (x)
>      __builtin_start_lifetime (&a.a.b[1]);
>    a.a.b[0][0] = 1;
>    a.a.b[1][1] = 3;
>    return a;
>  }
> -constexpr E e4 = fE3 (false);          // { dg-error "is not a constant
> expression" }
> -constexpr E e5 = fE3 (true);           // { dg-error "is not a constant
> expression" "" { target c++23_down } }
> +constexpr E e4 = fE3 (false);
> +constexpr E e5 = fE3 (true);
> +union H { int a; A b[2][2]; };
> +union I { int a; H b[2]; };
> +constexpr bool
> +fH (int x)
> +{
> +  G a;
> +  I b;
> +  switch (x)
> +    {
> +    case 0:
> +      __builtin_start_lifetime (&a.b);
> +      break;
> +    case 1:
> +      __builtin_start_lifetime (&a.b[0]);      // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> +      break;
> +    case 2:
> +      a.a = 42;
> +      __builtin_start_lifetime (&a.b);
> +      break;
> +    case 3:
> +      a.a = 42;
> +      __builtin_start_lifetime (&a.b[1]);      // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> +      break;
> +    case 4:
> +      __builtin_start_lifetime (&b.b);
> +      break;
> +    case 5:
> +      __builtin_start_lifetime (&b.b[0]);      // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> +      break;
> +    case 6:
> +      a.a = 42;
> +      __builtin_start_lifetime (&b.b);
> +      break;
> +    case 7:
> +      a.a = 42;
> +      __builtin_start_lifetime (&b.b[1]);      // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> +      break;
> +    case 8:
> +      __builtin_start_lifetime (&b.b[1].b[0][1]); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> +      break;
> +    case 9:
> +      __builtin_start_lifetime (&b.b);
> +      __builtin_start_lifetime (&b.b[1]);
> +      __builtin_start_lifetime (&b.b[1].b);
> +      __builtin_start_lifetime (&b.b[1].b[0]);
> +      __builtin_start_lifetime (&b.b[1].b[0][1]);
> +      break;
> +    case 10:
> +      __builtin_start_lifetime (&b.b);
> +      __builtin_start_lifetime (&b.b[1]);
> +      __builtin_start_lifetime (&b.b[1].b);
> +      __builtin_start_lifetime (&b.b[1].b[0][1]); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
>
Should the error say,  __builtin_start_lifetime with containing object not
withing lifetime?

> +      break;
> +    case 11:
> +      __builtin_start_lifetime (&b.b);
> +      __builtin_start_lifetime (&b.b[1]);
> +      __builtin_start_lifetime (&b.b[1].b[0]); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> +      break;
> +    case 12:
> +      __builtin_start_lifetime (&b.b);
> +      __builtin_start_lifetime (&b.b[1].b); // { dg-error
> "'__builtin_is_within_lifetime' with containing object not within lifetime"
> }
> +      break;
> +    default:
> +      break;
> +    }
> +  return true;
> +}
> +static_assert (fH (0));
> +constexpr bool h1 = fH (1);
> +static_assert (fH (2));
> +constexpr bool h3 = fH (3);
> +static_assert (fH (4));
> +constexpr bool h5 = fH (5);
> +static_assert (fH (6));
> +constexpr bool h7 = fH (7);
> +constexpr bool h8 = fH (8);
> +static_assert (fH (9));
> +constexpr bool h10 = fH (10);
> +constexpr bool h11 = fH (11);
> +constexpr bool h12 = fH (12);
> --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime8.C     2026-09-08
> 22:19:17.811118455 +0200
> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime8.C     2026-09-09
> 10:53:15.851010501 +0200
> @@ -59,6 +59,7 @@ foo ()
>    if (std::is_within_lifetime (&l.j.i[1].h.d[0].a))
>      return __LINE__;
>    std::start_lifetime (l.j);
> +  std::start_lifetime (l.j.i);
>    std::start_lifetime (l.j.i[1]);
>    if (!std::is_within_lifetime (&l.j.i[1]))
>      return __LINE__;
>
>
>         Jakub
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20260909/21439cb4/attachment-0001.htm>


More information about the Libstdc++ mailing list