On Mon, Sep 7, 2026 at 7:55 AM Tomasz Kaminski <[email protected]> wrote:

>
>
> On Fri, Sep 4, 2026 at 3:57 PM Jakub Jelinek <[email protected]> wrote:
>
>> Hi!
>>
>> Here is an updated version of the P3726R2 paper implementation, originally
>> posted in May together with the other trivial union paper and one CWG:
>> https://gcc.gnu.org/pipermail/gcc-patches/2026-May/thread.html#717349
>> That other paper and CWG has been committed in the meantime, this one
>> was deferred so that more accurate lifetime tracking is implemented (which
>> was partially done in the recently committed std::is_within_lifetime
>> patch).
>>
>> The major change from the earlier patch is that a new CONSTRUCTOR flag
>> CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P is added, which represents
>> both the C++20 std::allocator<T>::allocate required behavior as well as
>> P3726R2 std::start_lifetime, where the CONSTRUCTOR itself is considered
>> to be within lifetime, but none of its subobjects.
>> This flag is then tested in reduced_constant_expression_p to determine
>> which union elemental subobjects are actually inactive and only those are
>> allowed to be omitted, and in the recently added
>> __builtin_is_within_lifetime builtin constant evaluation.
>>
>> So far lightly tested, with:
>> GXX_TESTSUITE_STDS=98,11,14,17,20,23,26,29 make check-g++
>> RUNTESTFLAGS="dg.exp='within-lifetime* trivial-union* feat-cxx2*
>> constexpr-life*'"
>> ok for trunk if it passes full bootstrap/regtest?
>>
>> 2026-09-04  Jakub Jelinek  <[email protected]>
>>
>>         P3726R2
>>         PR c++/125845
>> gcc/c-family/
>>         * c-cppbuiltin.cc (c_cpp_builtins): For C++26 predefine
>>         __cpp_trivial_union to 202603L.
>> gcc/cp/
>>         * cp-tree.h: Implement C++26 P3726R2 - Adjustments to Union
>> Lifetime
>>         Rules.
>>         (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P): Define.
>>         (enum cp_built_in_function): Add CP_BUILT_IN_START_LIFETIME.
>>         (reduced_constant_expression_p): Add a bool argument defaulted to
>>         false.
>>         * tree.cc (builtin_valid_in_constant_expr_p): Handle
>>         CP_BUILT_IN_START_LIFETIME.
>>         * decl.cc (cxx_init_decl_processing): Create
>> __builtin_start_lifetime
>>         decl.
>>         * cp-gimplify.cc (cp_gimplify_expr): Handle
>>         CP_BUILT_IN_START_LIFETIME.
>>         * semantics.cc (finish_call_expr): Likewise.
>>         * constexpr.cc (cxx_eval_is_within_lifetime): For subobjects of
>>         CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P return
>> boolean_false_node.
>>         (cxx_eval_start_lifetime): New function.
>>         (cxx_eval_builtin_function_call): Handle
>> CP_BUILT_IN_START_LIFETIME.
>>         (reduced_constant_expression_p): Add union_elemental_subobject
>>         argument and propagate it to recursive calls.  If set, ignore
>> holes
>>         in array initializers which aren't within lifetime.
>>         (cxx_eval_store_expression): Add start_lifetime argument.  Don't
>>         preevaluate init in tha tcase, return void_node early if *valp
>>         is already non-NULL/void_node and within lifetime.
>>         (cxx_eval_constant_expression): For std::allocator<T>::allocate
>>         start lifetime of the created array but not of its subobjects.
>> gcc/testsuite/
>>         * g++.dg/cpp26/feat-cxx26.C: Adjust __cpp_trivial_union checking.
>>         * g++.dg/cpp29/feat-cxx29.C: Adjust __cpp_trivial_union checking.
>>         * g++.dg/cpp26/trivial-union3.C: New test.
>>         * g++.dg/cpp26/trivial-union4.C: New test.
>>         * g++.dg/cpp26/trivial-union5.C: New test.
>>         * g++.dg/cpp26/trivial-union6.C: New test.
>>         * g++.dg/cpp26/within-lifetime3.C: Remove #if 0 and #endif.
>>         * g++.dg/cpp29/feat-cxx29.C: Adjust __cpp_trivial_union checking.
>> libstdc++-v3/
>>         * include/bits/version.def (start_lifetime): New.
>>         * include/bits/version.h: Regenerate.
>>         * include/bits/stl_construct.h (std::start_lifetime): New function
>>         template.
>>         * include/std/memory: Define __glibcxx_want_start_lifetime before
>>         including bits/version.h.
>>         * src/c++23/std.cc.in: Add export std::start_lifetime.
>>
>> --- a/gcc/c-family/c-cppbuiltin.cc      2026-09-03 09:43:50.763477848
>> +0200
>> +++ b/gcc/c-family/c-cppbuiltin.cc      2026-09-04 13:54:14.074058589
>> +0200
>> @@ -1124,7 +1124,7 @@ c_cpp_builtins (cpp_reader *pfile)
>>             cpp_define (pfile, "__cpp_impl_reflection=202603L");
>>           else
>>             cpp_warn (pfile, "__cpp_impl_reflection");
>> -         cpp_define (pfile, "__cpp_trivial_union=202502L");
>> +         cpp_define (pfile, "__cpp_trivial_union=202603L");
>>         }
>>        if (cxx_dialect > cxx26)
>>         {
>> --- a/gcc/cp/cp-tree.h  2026-09-03 09:45:43.310945741 +0200
>> +++ b/gcc/cp/cp-tree.h  2026-09-04 14:11:50.412816957 +0200
>> @@ -5098,6 +5098,10 @@ get_vec_init_expr (tree t)
>>  #define CONSTRUCTOR_BRACES_ELIDED_P(NODE) \
>>    (CONSTRUCTOR_CHECK (NODE)->base.protected_flag)
>>
>> +/* True if omitted fields are considered to be not within lifetime.  */
>> +#define CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P(NODE) \
>> +  (CONSTRUCTOR_CHECK (NODE)->base.deprecated_flag)
>> +
>>  /* True if NODE represents a conversion for direct-initialization in a
>>     template.  Set by perform_implicit_conversion_flags.  */
>>  #define IMPLICIT_CONV_EXPR_DIRECT_INIT(NODE) \
>> @@ -7212,6 +7216,7 @@ enum cp_built_in_function {
>>    CP_BUILT_IN_CURRENT_EXCEPTION,
>>    CP_BUILT_IN_UNCAUGHT_EXCEPTIONS,
>>    CP_BUILT_IN_IS_WITHIN_LIFETIME,
>> +  CP_BUILT_IN_START_LIFETIME,
>>    CP_BUILT_IN_LAST
>>  };
>>
>> @@ -9413,7 +9418,7 @@ extern tree fold_non_dependent_init
>>  (tr
>>                                                  bool = false, tree =
>> NULL_TREE);
>>  extern tree fold_simple                                (tree);
>>  extern tree fold_to_constant                   (tree);
>> -extern bool reduced_constant_expression_p       (tree, tree = NULL_TREE);
>> +extern bool reduced_constant_expression_p       (tree, tree = NULL_TREE,
>> bool = false);
>>  extern bool is_instantiation_of_constexpr       (tree);
>>  extern bool var_in_constexpr_fn                 (tree);
>>  extern bool var_in_maybe_constexpr_fn           (tree);
>> --- a/gcc/cp/tree.cc    2026-09-03 09:43:50.789477496 +0200
>> +++ b/gcc/cp/tree.cc    2026-09-04 13:54:14.075685154 +0200
>> @@ -579,6 +579,7 @@ builtin_valid_in_constant_expr_p (const_
>>           case CP_BUILT_IN_CURRENT_EXCEPTION:
>>           case CP_BUILT_IN_UNCAUGHT_EXCEPTIONS:
>>           case CP_BUILT_IN_IS_WITHIN_LIFETIME:
>> +         case CP_BUILT_IN_START_LIFETIME:
>>             return true;
>>           default:
>>             break;
>> --- a/gcc/cp/decl.cc    2026-09-03 09:45:43.308945768 +0200
>> +++ b/gcc/cp/decl.cc    2026-09-04 13:54:14.077036599 +0200
>> @@ -5679,6 +5679,13 @@ cxx_init_decl_processing (void)
>>    set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF);
>>    SET_DECL_IMMEDIATE_FUNCTION_P (decl);
>>
>> +  tree void_vaftype = build_varargs_function_type_list
>> (boolean_type_node,
>> +                                                       NULL_TREE);
>> +  decl = add_builtin_function ("__builtin_start_lifetime",
>> +                              void_vaftype, CP_BUILT_IN_START_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-09-03 09:43:50.774477699 +0200
>> +++ b/gcc/cp/cp-gimplify.cc     2026-09-04 13:54:14.078081642 +0200
>> @@ -1036,6 +1036,7 @@ cp_gimplify_expr (tree *expr_p, gimple_s
>>                                                                     0));
>>                 break;
>>               case CP_BUILT_IN_CONSTEXPR_DIAG:
>> +             case CP_BUILT_IN_START_LIFETIME:
>>                 *expr_p = void_node;
>>                 break;
>>               default:
>> --- a/gcc/cp/semantics.cc       2026-09-03 09:45:43.312945713 +0200
>> +++ b/gcc/cp/semantics.cc       2026-09-04 14:10:43.423660866 +0200
>> @@ -3541,30 +3541,74 @@ finish_call_expr (tree fn, vec<tree, va_
>>             }
>>
>>           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");
>> +             && fndecl_built_in_p (fn, BUILT_IN_FRONTEND))
>> +           switch (DECL_UNCHECKED_FUNCTION_CODE (fn))
>> +             {
>> +               tree arg, ptype, type;
>> +             case CP_BUILT_IN_IS_WITHIN_LIFETIME:
>> +               /* 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 ("%qs needs a single argument",
>> +                            "__builtin_is_within_lifetime");
>> +                   return error_mark_node;
>> +                 }
>> +               arg = (**args)[0];
>> +               if (error_operand_p (arg))
>>                   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);
>> +               ptype = TREE_TYPE (arg);
>> +               if (!POINTER_TYPE_P (ptype))
>> +                 {
>> +                   if (complain & tf_error)
>> +                     error ("%qs argument type %qT is not pointer type",
>> +                            "__builtin_is_within_lifetime", ptype);
>> +                   return error_mark_node;
>> +                 }
>> +               break;
>> +             case CP_BUILT_IN_START_LIFETIME:
>> +               /* Unless users call the builtin directly, the following
>> 2 checks
>> +                  should be ensured from std::start_lifetime template.
>> */
>> +               if (vec_safe_length (*args) != 1)
>> +                 {
>> +                   if (complain & tf_error)
>> +                     error ("%qs needs a single argument",
>> +                            "__builtin_start_lifetime");
>> +                   return error_mark_node;
>> +                 }
>> +               arg = (**args)[0];
>> +               if (error_operand_p (arg))
>>                   return error_mark_node;
>> -               }
>> -           }
>> +               ptype = TREE_TYPE (arg);
>> +               if (!POINTER_TYPE_P (ptype))
>> +                 {
>> +                   if (complain & tf_error)
>> +                     error ("%qs argument type %qT is not pointer type",
>> +                            "__builtin_start_lifetime", ptype);
>> +                   return error_mark_node;
>> +                 }
>> +               type = TREE_TYPE (ptype);
>> +               if (!complete_type_or_else (type, NULL_TREE))
>> +                 return error_mark_node;
>> +               if (!CP_AGGREGATE_TYPE_P (type))
>> +                 {
>> +                   if (complain & tf_error)
>> +                     error ("%qs argument type %qT is not a pointer to "
>> +                            "aggregate type", "__builtin_start_lifetime",
>> +                            ptype);
>> +                   return error_mark_node;
>> +                 }
>> +               if (!implicit_lifetime_type_p (type))
>> +                 {
>> +                   if (complain & tf_error)
>> +                     error ("%qs argument type %qT is not a pointer to "
>> +                            "implicit-lifetime type",
>> +                            "__builtin_start_lifetime", ptype);
>> +                   return error_mark_node;
>> +                 }
>> +               break;
>> +             }
>>
>>           /* A call to a namespace-scope function.  */
>>           result = build_new_function_call (fn, args, orig_complain);
>> --- a/gcc/cp/constexpr.cc       2026-09-03 09:43:50.766477808 +0200
>> +++ b/gcc/cp/constexpr.cc       2026-09-04 14:32:59.700779280 +0200
>> @@ -2748,7 +2748,11 @@ cxx_eval_is_within_lifetime (const const
>>         if (ref == NULL_TREE)
>>           continue;
>>         if (CONSTRUCTOR_NO_CLEARING (val))
>> -         val = val_uninit;
>> +         {
>> +           if (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (val))
>> +             return boolean_false_node;
>> +           val = val_uninit;
>> +         }
>>         else
>>           val = val_zero_init;
>>         continue;
>> @@ -2775,7 +2779,11 @@ cxx_eval_is_within_lifetime (const const
>>             continue;
>>           }
>>         if (CONSTRUCTOR_NO_CLEARING (val))
>> -         val = val_uninit;
>> +         {
>> +           if (CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (val))
>> +             return boolean_false_node;
>> +           val = val_uninit;
>> +         }
>>         else
>>           val = val_zero_init;
>>         continue;
>> @@ -2785,6 +2793,41 @@ cxx_eval_is_within_lifetime (const const
>>    return boolean_true_node;
>>  }
>>
>> +static tree cxx_eval_store_expression (const constexpr_ctx *, tree,
>> +                                      value_cat, bool *, bool *,
>> +                                      tree *, bool = false);
>> +
>> +/* Attempt to evaluate T which represents a call to
>> +   __builtin_start_lifetime.  */
>> +
>> +static tree
>> +cxx_eval_start_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 = 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;
>> +  tree ctor = build_constructor (TREE_TYPE (arg), NULL);
>> +  CONSTRUCTOR_NO_CLEARING (ctor) = 1;
>> +  CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (ctor) = 1;
>> +  tree modexpr = build2_loc (loc, MODIFY_EXPR, TREE_TYPE (arg), arg,
>> ctor);
>> +  arg = cxx_eval_store_expression (ctx, modexpr, vc_discard,
>> non_constant_p,
>> +                                  overflow_p, jump_target, true);
>> +  ggc_free (modexpr);
>> +  if (*jump_target)
>> +    return NULL_TREE;
>> +  if (*non_constant_p)
>> +    return t;
>> +  return void_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.  */
>> @@ -2862,6 +2905,10 @@ cxx_eval_builtin_function_call (const co
>>         return cxx_eval_is_within_lifetime (ctx, t, non_constant_p,
>> overflow_p,
>>                                             jump_target);
>>
>> +      case CP_BUILT_IN_START_LIFETIME:
>> +       return cxx_eval_start_lifetime (ctx, t, non_constant_p,
>> overflow_p,
>> +                                       jump_target);
>> +
>>        default:
>>         break;
>>        }
>> @@ -5107,10 +5154,13 @@ cxx_eval_call_expression (const constexp
>>     initializes all the members, the CONSTRUCTOR_NO_CLEARING flag will be
>>     cleared.  If called recursively on a FIELD_DECL's CONSTRUCTOR, SZ
>>     is DECL_SIZE of the FIELD_DECL, otherwise NULL.
>> +   UNION_ELEMENTAL_SUBOBJECT is true when recursing on C++ >= 26 union
>> +   elemental subobjects.
>>     FIXME speed this up, it's taking 16% of compile time on sieve
>> testcase.  */
>>
>>  bool
>> -reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */)
>> +reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */,
>> +                              bool union_elemental_subobject /* = false
>> */)
>>  {
>>    if (t == NULL_TREE)
>>      return false;
>> @@ -5134,7 +5184,11 @@ reduced_constant_expression_p (tree t, t
>>         return false;
>>        if (CONSTRUCTOR_NO_CLEARING (t))
>>         {
>> -         if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE)
>> +         if (union_elemental_subobject
>> +             && TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE
>> +             && CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (t))
>> +           field = NULL_TREE;
>> +         else if (TREE_CODE (TREE_TYPE (t)) == ARRAY_TYPE)
>>             {
>>               /* There must be a valid constant initializer at every array
>>                  index.  */
>> @@ -5168,12 +5222,25 @@ reduced_constant_expression_p (tree t, t
>>                  active member has no constituent values, so all
>> constituent
>>                  values are constant.  */
>>               field = NULL_TREE;
>> +             union_elemental_subobject = cxx_dialect >= cxx26;
>>             }
>>           else
>> -           field = next_subobject_field (TYPE_FIELDS (TREE_TYPE (t)));
>> +           {
>> +             field = next_subobject_field (TYPE_FIELDS (TREE_TYPE (t)));
>> +             union_elemental_subobject = false;
>> +           }
>>         }
>>        else
>> -       field = NULL_TREE;
>> +       {
>> +         field = NULL_TREE;
>> +         if (cxx_dialect >= cxx26)
>> +           {
>> +             if (TREE_CODE (TREE_TYPE (t)) == UNION_TYPE)
>> +               union_elemental_subobject = true;
>> +             else if (TREE_CODE (TREE_TYPE (t)) != ARRAY_TYPE)
>> +               union_elemental_subobject = false;
>> +           }
>> +       }
>>        for (auto &e: CONSTRUCTOR_ELTS (t))
>>         {
>>           /* If VAL is null, we're in the middle of initializing this
>> @@ -5183,7 +5250,8 @@ reduced_constant_expression_p (tree t, t
>>                                                && (TREE_CODE (e.index)
>>                                                    == FIELD_DECL))
>>                                               ? DECL_SIZE (e.index)
>> -                                             : NULL_TREE))
>> +                                             : NULL_TREE,
>> +                                             union_elemental_subobject))
>>             return false;
>>           /* We want to remove initializers for empty fields in a struct
>> to
>>              avoid confusing output_constructor.  */
>> @@ -8213,13 +8281,14 @@ modifying_const_object_p (tree_code code
>>    return false;
>>  }
>>
>> -/* Evaluate an INIT_EXPR or MODIFY_EXPR.  */
>> +/* Evaluate an INIT_EXPR or MODIFY_EXPR.  START_LIFETIME is true
>> +   when evaluating __builtin_start_lifetime.  */
>>
>>  static tree
>>  cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
>>                            value_cat lval,
>>                            bool *non_constant_p, bool *overflow_p,
>> -                          tree *jump_target)
>> +                          tree *jump_target, bool
>> start_lifetime/*=false*/)
>>  {
>>    constexpr_ctx new_ctx = *ctx;
>>
>> @@ -8232,7 +8301,7 @@ cxx_eval_store_expression (const constex
>>
>>    tree type = TREE_TYPE (target);
>>    bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) == MODIFY_EXPR;
>> -  if (preeval && !TREE_CLOBBER_P (init))
>> +  if (preeval && !TREE_CLOBBER_P (init) && !start_lifetime)
>>      {
>>        /* Ignore var = .DEFERRED_INIT (); for now, until PR121965 is
>> fixed.  */
>>        if (flag_auto_var_init > AUTO_INIT_UNINITIALIZED
>> @@ -8573,12 +8642,13 @@ cxx_eval_store_expression (const constex
>>                           index);
>>               *non_constant_p = true;
>>             }
>> -         else if (!is_access_expr
>> -                  || (TREE_CLOBBER_P (init)
>> -                      && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END)
>> -                  || (TREE_CODE (t) == MODIFY_EXPR
>> -                      && CLASS_TYPE_P (inner)
>> -                      && !type_has_non_deleted_trivial_default_ctor
>> (inner)))
>> +         else if ((!is_access_expr
>> +                   || (TREE_CLOBBER_P (init)
>> +                       && CLOBBER_KIND (init) >= CLOBBER_OBJECT_END)
>> +                   || (TREE_CODE (t) == MODIFY_EXPR
>> +                       && CLASS_TYPE_P (inner)
>> +                       && !type_has_non_deleted_trivial_default_ctor
>> (inner)))
>> +                  && !start_lifetime)
>>             {
>>               /* Diagnose changing active union member after
>> initialization
>>                  without a valid member access expression, as described in
>> @@ -8675,6 +8745,11 @@ cxx_eval_store_expression (const constex
>>        empty_base = true;
>>      }
>>
>> +  /* For __builtin_start_lifetime, if it is already within lifetime,
>> +     don't do anything.  */
>> +  if (start_lifetime && *valp != NULL_TREE && *valp != void_node)
>> +    return void_node;
>> +
>>    /* Detect modifying a constant object in constexpr evaluation.
>>       We have found a const object that is being modified.  Figure out
>>       if we need to issue an error.  Consider
>> @@ -8780,7 +8855,7 @@ cxx_eval_store_expression (const constex
>>                        || *valp == void_node
>>                        || (same_type_ignoring_top_level_qualifiers_p
>>                            (TREE_TYPE (*valp), type)));
>> -  if (empty_base)
>> +  if (empty_base && !start_lifetime)
>>      {
>>        /* Just evaluate the initializer and return, since there's no
>> actual data
>>          to store, and we didn't build a CONSTRUCTOR.  */
>> @@ -10730,6 +10805,17 @@ cxx_eval_constant_expression (const cons
>>             TREE_TYPE (var) = new_type;
>>             TREE_TYPE (TREE_OPERAND (op, 0))
>>               = build_pointer_type (TREE_TYPE (var));
>> +           if (is_std_allocator_allocate (ctx->call)
>> +               && id_equal (DECL_NAME (ctx->call->fundef->decl),
>> "allocate"))
>> +             {
>> +               /* [allocator.members]/5: This function starts the
>> lifetime
>> +                  of the array object, but not that of any of the array
>> +                  elements.  */
>> +               tree ctor = build_constructor (new_type, NULL);
>> +               CONSTRUCTOR_NO_CLEARING (ctor) = 1;
>> +               CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P (ctor) = 1;
>> +               ctx->global->put_value (var, ctor);
>> +             }
>>           }
>>
>>         /* This can happen for std::meta::info(^^int) where the cast has
>> no
>> --- a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C   2026-09-03
>> 09:43:50.797477387 +0200
>> +++ b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C   2026-09-04
>> 13:54:14.080797712 +0200
>> @@ -655,8 +655,8 @@
>>
>>  #ifndef __cpp_trivial_union
>>  #  error "__cpp_trivial_union"
>> -#elif __cpp_trivial_union != 202502
>> -#  error "__cpp_trivial_union != 202502"
>> +#elif __cpp_trivial_union != 202603
>> +#  error "__cpp_trivial_union != 202603"
>>  #endif
>>
>>  // C++26 attributes:
>> --- a/gcc/testsuite/g++.dg/cpp26/trivial-union3.C       2026-09-04
>> 13:54:14.080919178 +0200
>> +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union3.C       2026-09-04
>> 13:54:14.080919178 +0200
>> @@ -0,0 +1,28 @@
>> +// P3074R7 - trivial unions (was std::uninitialized<T>)
>> +// P3726R2 - Adjustments to Union Lifetime Rules
>> +// { dg-do compile { target c++26 } }
>> +
>> +#include <memory>
>> +#include <string>
>> +
>> +template <typename T, size_t N>
>> +struct FixedVector {
>> +  union { T storage[N]; };
>> +  size_t size = 0;
>> +
>> +  constexpr FixedVector () { std::start_lifetime (storage); }
>> +
>> +  constexpr ~FixedVector() { std::destroy(storage, storage + size); }
>> +
>> +  constexpr void push_back(T const& v) { ::new (storage + size) T(v);
>> ++size; }
>> +};
>> +
>> +constexpr size_t
>> +silly_test ()
>> +{
>> +  FixedVector <std::string, 3> v;
>> +  v.push_back ("some sufficiently longer string");
>> +  return v.size;
>> +}
>> +
>> +static_assert (silly_test () == 1);
>> --- a/gcc/testsuite/g++.dg/cpp26/trivial-union4.C       2026-09-04
>> 13:54:14.081027425 +0200
>> +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union4.C       2026-09-04
>> 13:54:14.081027425 +0200
>> @@ -0,0 +1,63 @@
>> +// P3074R7 - trivial unions (was std::uninitialized<T>)
>> +// P3726R2 - Adjustments to Union Lifetime Rules
>> +// { dg-do compile { target c++26 } }
>> +
>> +namespace std {
>> +  template <class T>
>> +  constexpr void
>> +  start_lifetime (T &r) noexcept
>> +  {
>> +    __builtin_start_lifetime (__builtin_addressof (r));
>> +  }
>> +}
>> +
>> +struct S { int a; };
>> +union U { int a; S b[3]; };
>> +union V { int a; U b[2]; };
>> +
>> +consteval
>> +{
>> +  S s;
>> +  s.a = 42;
>> +  std::start_lifetime (s);
>> +  if (s.a != 42)
>> +    throw 1;
>> +  U u;
>> +  u.b[0].a = 42;
>> +  std::start_lifetime (u.b);
>> +  std::start_lifetime (u.b[0]);
>> +  std::start_lifetime (u.b[2]);
>> +  u.a = 42;
>> +  std::start_lifetime (u.b);
>> +  std::start_lifetime (u.b[0]);
>> +  std::start_lifetime (u.b[1]);
>> +  V v;
>> +  v.a = 42;
>> +  std::start_lifetime (v.b);
>> +  std::start_lifetime (v.b[1]);
>> +  v.b[1].a = 42;
>> +  std::start_lifetime (v.b[1].b);
>> +  std::start_lifetime (v.b[1].b[1]);
>> +  v.b[1].b[1].a = 43;
>> +  std::start_lifetime (v.b);
>> +  std::start_lifetime (v.b[1].b[1]);
>> +  if (v.b[1].b[1].a != 43)
>> +    throw 2;
>> +  const S cs = { 42 };
>> +  std::start_lifetime (cs);
>> +  if (cs.a != 42)
>> +    throw 3;
>> +  const U cu = { .b = { { 41 }, { 42 }, { 43 } } };
>> +  std::start_lifetime (cu.b);
>> +  std::start_lifetime (cu.b[0]);
>> +  std::start_lifetime (cu.b[2]);
>> +  if (cu.b[2].a != 43)
>> +    throw 4;
>> +  const V cv = { .b = { { .b = { { 44 } } } } };
>> +  std::start_lifetime (cv.b);
>> +  std::start_lifetime (cv.b[0]);
>> +  std::start_lifetime (cv.b[0].b);
>> +  std::start_lifetime (cv.b[0].b[0]);
>> +  if (cv.b[0].b[0].a != 44)
>> +    throw 5;
>> +}
>> --- a/gcc/testsuite/g++.dg/cpp26/trivial-union5.C       2026-09-04
>> 13:54:14.081120178 +0200
>> +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union5.C       2026-09-04
>> 13:54:14.081120178 +0200
>> @@ -0,0 +1,37 @@
>> +// P3074R7 - trivial unions (was std::uninitialized<T>)
>> +// P3726R2 - Adjustments to Union Lifetime Rules
>> +// { dg-do compile { target c++26 } }
>> +
>> +struct S { int a; };
>> +S a = { 5 };
>> +struct T { constexpr T () : a (42) {} int a; };
>> +struct R { int a; constexpr ~R () {} };
>> +union U { int a; S b[2]; };
>> +
>> +consteval { int a = 5; __builtin_start_lifetime (&a); }
>> // { dg-error "'__builtin_start_lifetime' argument type 'int\\\*' is not a
>> pointer to aggregate type" }
>> +consteval { __builtin_start_lifetime (&a); };                  // {
>> dg-error "modification of 'a' from outside current evaluation is not a
>> constant expression" }
>> +consteval { __builtin_start_lifetime (); };                    // {
>> dg-error "'__builtin_start_lifetime' needs a single argument" }
>> +consteval { S a = { 6 }; __builtin_start_lifetime (&a, &a); }; // {
>> dg-error "'__builtin_start_lifetime' needs a single argument" }
>> +consteval { __builtin_start_lifetime (42); };                  // {
>> dg-error "'__builtin_start_lifetime' argument type 'int' is not pointer
>> type" }
>> +consteval { T t; __builtin_start_lifetime (&t); }              // {
>> dg-error "'__builtin_start_lifetime' argument type 'T\\\*' is not a pointer
>> to aggregate type" }
>> +consteval { R r; __builtin_start_lifetime (&r); }              // {
>> dg-error "'__builtin_start_lifetime' argument type 'R\\\*' is not a pointer
>> to implicit-lifetime type" }
>> +consteval { const U u = { .a = 42 }; __builtin_start_lifetime (&u.a); }
>>       // { dg-error "'__builtin_start_lifetime' argument type 'const
>> int\\\*' is not a pointer to aggregate type" }
>> +consteval { const U u = { .a = 42 }; __builtin_start_lifetime (&u.b); }
>>       // { dg-error "modifying a const object 'u.U::b' is not allowed in a
>> constant expression" }
>> +
>> +void
>> +foo ()
>> +{
>> +  S s;
>> +  __builtin_start_lifetime (&s);
>> +  int a = 5;
>> +  __builtin_start_lifetime (&a);               // { dg-error
>> "'__builtin_start_lifetime' argument type 'int\\\*' is not a pointer to
>> aggregate type" }
>> +  __builtin_start_lifetime (&::a);
>> +  __builtin_start_lifetime ();                 // { dg-error
>> "'__builtin_start_lifetime' needs a single argument" }
>> +  S b = { 6 };
>> +  __builtin_start_lifetime (&b, &b);           // { dg-error
>> "'__builtin_start_lifetime' needs a single argument" }
>> +  __builtin_start_lifetime (42);               // { dg-error
>> "'__builtin_start_lifetime' argument type 'int' is not pointer type" }
>> +  T t;
>> +  __builtin_start_lifetime (&t);               // { dg-error
>> "'__builtin_start_lifetime' argument type 'T\\\*' is not a pointer to
>> aggregate type" }
>> +  R r;
>> +  __builtin_start_lifetime (&r);               // { dg-error
>> "'__builtin_start_lifetime' argument type 'R\\\*' is not a pointer to
>> implicit-lifetime type" }
>> +}
>> --- a/gcc/testsuite/g++.dg/cpp26/trivial-union6.C       2026-09-04
>> 13:54:14.081215755 +0200
>> +++ b/gcc/testsuite/g++.dg/cpp26/trivial-union6.C       2026-09-04
>> 13:54:14.081215755 +0200
>> @@ -0,0 +1,70 @@
>> +// P3074R7 - trivial unions (was std::uninitialized<T>)
>> +// P3726R2 - Adjustments to Union Lifetime Rules
>> +// { dg-do compile { target c++20 } }
>> +
>> +struct A { int a[2]; };
>> +union B { int a; int b[2]; };
>> +union C { int a; int b[2][2]; };
>> +struct D { B a; };
>> +struct E { C a; };
>> +union F { int a; A b; };
>> +union G { int a; A b[2]; };
>> +
>> +constexpr A fA () { A a; a.a[0] = 1; return a; }
>> +constexpr A a = fA ();         // { dg-error "is not a constant
>> expression" "" { xfail *-*-* } }
>> +constexpr B fB () { B a; a.b[0] = 1; return a; }
>> +constexpr B b = fB ();         // { dg-error "is not a constant
>> expression" }
>
> +constexpr C fC () { C a; a.b[0][0] = 1; a.b[1][0] = 2; a.b[1][1] = 3;
>> return a; }
>> +constexpr C c = fC ();         // { dg-error "is not a constant
>> expression" }
>> +constexpr D fD () { D a; a.a.b[0] = 1; return a; }
>> +constexpr D d = fD ();         // { dg-error "is not a constant
>> expression" }
>> +constexpr E fE () { E a; a.a.b[0][0] = 1; a.a.b[1][0] = 2; a.a.b[1][1] =
>> 3; return a; }
>>
> I would repurpose this test, and do:
E a;
start_lifetime(&a.a.b[0]);
a.a.b[0][0] = 1;
// a.a.b[0][1] is not withing lifetime so OK
a.a.b[1][1] = 3;
return a;
// This should still be ill-formed due a.a.b[1][0] being erronous.


> +constexpr E e = fE ();         // { dg-error "is not a constant
>> expression" }
>> +constexpr F fF () { F a; a.b.a[0] = 1; return a; }
>> +constexpr F f = fF ();         // { dg-error "is not a constant
>> expression" }
>> +constexpr G fG () { G a; a.b[0].a[0] = 1; return a; }
>> +constexpr G g = fG ();         // { dg-error "is not a constant
>> expression" }
>> +constexpr B
>> +fB2 ()
>> +{
>> +  B a;
>> +  __builtin_start_lifetime (&a.b);
>> +  a.b[0] = 1;
>> +  return a;
>> +}
>> +constexpr B b2 = fB2 ();       // { dg-error "is not a constant
>> expression" "" { target c++23_down } }
>> +constexpr C
>> +fC2 ()
>> +{
>> +  C a;
>> +  __builtin_start_lifetime (&a.b);
>> +  __builtin_start_lifetime (&a.b[0]);
>> +  __builtin_start_lifetime (&a.b[1]);
>>
> Could you add test where you remove this start lifetime?
>
>> +  a.b[0][0] = 1;
>> +  a.b[1][0] = 2;
>> +  a.b[1][1] = 3;
>>
> Then the first of above assignments will create an a.b[1] array object,
> and second
> will make it fully uninitialized. The other combiation would be to keep
> start_lifetime on
> &a.b[1]. but remove one of the assignments. To avoid repetition you could
> check one combination
> in fC2 and other in fE2, as they both now test same scenario.
>
>> +  return a;
>> +}
>> +constexpr C c2 = fC2 ();       // { dg-error "is not a constant
>> expression" "" { target c++23_down } }
>> +constexpr D
>> +fD2 ()
>> +{
>> +  D a;
>> +  __builtin_start_lifetime (&a.a.b);
>> +  a.a.b[0] = 1;
>> +  return a;
>> +}
>> +constexpr D d2 = fD2 ();       // { dg-error "is not a constant
>> expression" "" { target c++23_down } }
>> +constexpr E
>> +fE2 ()
>> +{
>> +  E a;
>> +  __builtin_start_lifetime (&a.a.b);
>> +  __builtin_start_lifetime (&a.a.b[0]);
>> +  __builtin_start_lifetime (&a.a.b[1]);
>> +  a.a.b[0][0] = 1;
>> +  a.a.b[1][0] = 2;
>> +  a.a.b[1][1] = 3;
>> +  return a;
>> +}
>> +constexpr E e2 = fE2 ();       // { dg-error "is not a constant
>> expression" "" { target c++23_down } }
>> --- a/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C     2026-09-03
>> 09:43:50.797477387 +0200
>> +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C     2026-09-04
>> 13:54:14.081341081 +0200
>> @@ -17,12 +17,10 @@ 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;
>> --- a/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C   2026-09-03
>> 09:43:50.797477387 +0200
>> +++ b/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C   2026-09-04
>> 13:54:14.081504434 +0200
>> @@ -655,8 +655,8 @@
>>
>>  #ifndef __cpp_trivial_union
>>  #  error "__cpp_trivial_union"
>> -#elif __cpp_trivial_union != 202502
>> -#  error "__cpp_trivial_union != 202502"
>> +#elif __cpp_trivial_union != 202603
>> +#  error "__cpp_trivial_union != 202603"
>>  #endif
>>
>>  // C++26 attributes:
>> --- a/libstdc++-v3/include/bits/version.def     2026-09-03
>> 09:43:50.805477279 +0200
>> +++ b/libstdc++-v3/include/bits/version.def     2026-09-04
>> 13:54:14.114036138 +0200
>> @@ -2661,6 +2661,15 @@ ftms = {
>>    };
>>  };
>>
>> +ftms = {
>> +  name = start_lifetime;
>> +  values = {
>> +    v = 202603;
>> +    cxxmin = 26;
>> +    extra_cond = "__has_builtin(__builtin_start_lifetime)";
>> +  };
>> +};
>> +
>>  // Standard test specifications.
>>  stds[97] = ">= 199711L";
>>  stds[03] = ">= 199711L";
>> --- a/libstdc++-v3/include/bits/version.h       2026-09-03
>> 09:43:50.805477279 +0200
>> +++ b/libstdc++-v3/include/bits/version.h       2026-09-04
>> 13:54:14.115036126 +0200
>> @@ -2949,4 +2949,14 @@
>>  #endif /* !defined(__cpp_lib_hardened_view_interface) */
>>  #undef __glibcxx_want_hardened_view_interface
>>
>> +#if !defined(__cpp_lib_start_lifetime)
>> +# if (__cplusplus >  202302L) &&
>> (__has_builtin(__builtin_start_lifetime))
>> +#  define __glibcxx_start_lifetime 202603L
>> +#  if defined(__glibcxx_want_all) ||
>> defined(__glibcxx_want_start_lifetime)
>> +#   define __cpp_lib_start_lifetime 202603L
>> +#  endif
>> +# endif
>> +#endif /* !defined(__cpp_lib_start_lifetime) */
>> +#undef __glibcxx_want_start_lifetime
>> +
>>  #undef __glibcxx_want_all
>> --- a/libstdc++-v3/include/bits/stl_construct.h 2026-09-03
>> 09:43:50.803477306 +0200
>> +++ b/libstdc++-v3/include/bits/stl_construct.h 2026-09-04
>> 13:54:14.116046285 +0200
>> @@ -412,6 +412,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>      }
>>  #endif // C++23
>>
>> +#if __glibcxx_start_lifetime >= 202603L // C++ >= 26
>> +  template <typename _Tp>
>> +    constexpr void
>> +    start_lifetime(_Tp& __r) noexcept
>> +    {
>> +#ifdef __cpp_lib_is_implicit_lifetime
>> +      static_assert(std::is_implicit_lifetime_v<_Tp>);
>> +#endif
>> +#ifdef __cpp_lib_is_aggregate
>> +      static_assert(std::is_aggregate_v<_Tp>);
>> +#endif
>> +      __builtin_start_lifetime(__builtin_addressof(__r));
>> +    }
>> +#endif // C++26
>> +
>>  _GLIBCXX_END_NAMESPACE_VERSION
>>  } // namespace std
>>
>> --- a/libstdc++-v3/include/std/memory   2026-09-03 09:43:50.811477198
>> +0200
>> +++ b/libstdc++-v3/include/std/memory   2026-09-04 13:54:14.116326618
>> +0200
>> @@ -127,6 +127,7 @@
>>  #define __glibcxx_want_transparent_operators
>>  #define __glibcxx_want_smart_ptr_owner_equality
>>  #define __glibcxx_want_allocate_at_least
>> +#define __glibcxx_want_start_lifetime
>>  #include <bits/version.h>
>>
>>  #if __cplusplus >= 201103L && __cplusplus <= 202002L && _GLIBCXX_HOSTED
>> --- a/libstdc++-v3/src/c++23/std.cc.in  2026-09-03 09:43:50.811477198
>> +0200
>> +++ b/libstdc++-v3/src/c++23/std.cc.in  2026-09-04 13:54:14.116872978
>> +0200
>> @@ -2074,6 +2074,9 @@ export namespace std
>>  #ifdef __glibcxx_allocate_at_least
>>    using std::allocation_result;
>>  #endif
>> +#if __cpp_lib_start_lifetime
>> +  using std::start_lifetime;
>> +#endif
>>  }
>>
>>  // 20.4 <memory_resource>
>>
>>         Jakub
>>
>>

Reply via email to