[PATCH] c++, libstdc++, v4: Implement C++26 P3726R2 - Adjustments to Union Lifetime Rules [PR125845]
Jakub Jelinek
jakub@redhat.com
Wed Sep 9 07:45:16 GMT 2026
On Tue, Sep 08, 2026 at 04:50:09PM -0400, Jason Merrill wrote:
> > > > The init in that case is not a clobber, but an empty CONSTRUCTOR with
> > > > the right flags. Making start_lifetime implied from the
> > > > CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P bit wouldn't be reliable.
> > > > What we could do (but not done in the patch) is use some tree other than
> > > > MODIFY_EXPR/INIT_EXPR with 2+ operands instead of the current MODIFY_EXPR,
> > > > say MODOP_EXPR and let that mean start_lifetime.
> > >
> > > I like the sound of that, but won't insist on it.
> >
> > Ok, will do that tomorrow.
Here it is. The other part (i.e. to make start_lifetime act more like
start_lifetime first on outer subobject if any or as Tomasz requests instead
as if !is_within_lifetime *non_constant_p = true; for the latter, guess I
could move parts of cxx_eval_is_within_lifetime to a helper function and
call it before trying to evaluate cxx_eval_store_expression on the
MODOP_EXPR) not done yet though.
Bootstrapped/regtested on x86_64-linux and i686-linux.
2026-09-09 Jakub Jelinek <jakub@redhat.com>
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. Document some generic flags used in C++ specific way on some
trees in the toplevel comment.
(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 (check_frontend_builtin): New function.
(finish_call_expr): Use it.
* 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.
(is_std_allocator_allocate): Only return true for
std::allocator<T>::allocate and not for std::allocator<T>::deallocate.
(is_std_allocator_allocate_deallocate): New functions.
(cxx_eval_call_expression): Use is_std_allocator_allocate_deallocate
rather than is_std_allocator_allocate.
(reduced_constant_expression_p): Add union_elemental_subobject
argument and propagate it to recursive calls. If set, ignore for
C++26 holes in array initializers which aren't within lifetime.
(modifying_const_object_p): Change != MODIFY_EXPR test to == INIT_EXPR.
(cxx_eval_store_expression): Handle MODOP_EXPR next to INIT_EXPR and
MODIFY_EXPR, meaning start_lifetime. Don't preevaluate init in that
case, 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.
Use is_std_allocator_allocate_deallocate rather than
is_std_allocator_allocate where appropriate.
(potential_constant_expression_1): Likewise.
gcc/testsuite/
* g++.dg/cpp26/feat-cxx26.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/cpp26/within-lifetime8.C: New test.
* g++.dg/cpp26/within-lifetime9.C: New test.
* 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-08 12:51:58.330351145 +0200
+++ b/gcc/c-family/c-cppbuiltin.cc 2026-09-08 22:19:17.800398780 +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-08 22:18:28.476021605 +0200
+++ b/gcc/cp/cp-tree.h 2026-09-08 22:19:17.802283696 +0200
@@ -644,7 +644,22 @@ extern GTY(()) tree cp_global_trees[CPTI
Temporarily, it may be set to a TREE_LIST whose TREE_VALUE is
the virtual function this one overrides, and whose TREE_CHAIN is
- the old DECL_VINDEX. */
+ the old DECL_VINDEX.
+
+ TREE_PRIVATE
+ For CONSTRUCTOR, this is CONSTRUCTOR_IS_PAREN_INIT.
+
+ TREE_PROTECTED
+ For CONSTRUCTOR, this is CONSTRUCTOR_BRACES_ELIDED_P.
+ For IDENTIFIER, this is IDENTIFIER_LAMBDA_P.
+ For AGGR_INIT_EXPR, this is AGGR_INIT_FROM_THUNK_P.
+ For ADDR_EXPR, this is ADDR_EXPR_DENOTES_CALL_P.
+
+ TREE_STATIC
+ For AGGR_INIT_EXPR, this is AGGR_INIT_EXPR_MUST_TAIL.
+
+ TREE_DEPRECATED
+ For CONSTRUCTOR, this is CONSTRUCTOR_OMITTED_NOT_WITHIN_LIFETIME_P. */
/* Language-specific tree checkers. */
@@ -5099,6 +5114,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) \
@@ -7231,6 +7250,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
};
@@ -9432,7 +9452,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-08 12:51:58.341351001 +0200
+++ b/gcc/cp/tree.cc 2026-09-08 22:19:17.803294943 +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-08 22:18:28.478021579 +0200
+++ b/gcc/cp/decl.cc 2026-09-08 22:39:22.181185668 +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 (void_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 (integer_type_node, 2);
/* Guess at the initial static decls size. */
--- a/gcc/cp/cp-gimplify.cc 2026-09-08 22:18:28.475021617 +0200
+++ b/gcc/cp/cp-gimplify.cc 2026-09-08 22:19:17.807495585 +0200
@@ -1040,6 +1040,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-08 22:18:28.488021453 +0200
+++ b/gcc/cp/semantics.cc 2026-09-08 22:19:17.808394407 +0200
@@ -3300,6 +3300,82 @@ perform_koenig_lookup (cp_expr fn_expr,
return cp_expr (fn, loc);
}
+/* Analyze call to a front-end builtin FN with arguments *ARGS. Return true
+ if the call is correct, false if erroneous. */
+
+static bool
+check_frontend_builtin (tree fn, vec<tree, va_gc> **args,
+ tsubst_flags_t complain)
+{
+ tree arg, ptype, type;
+ switch (DECL_UNCHECKED_FUNCTION_CODE (fn))
+ {
+ 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 false;
+ }
+ arg = (**args)[0];
+ if (error_operand_p (arg))
+ return false;
+ 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 false;
+ }
+ return true;
+ 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 false;
+ }
+ arg = (**args)[0];
+ if (error_operand_p (arg))
+ return false;
+ 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 false;
+ }
+ type = TREE_TYPE (ptype);
+ if (!complete_type_or_else (type, NULL_TREE))
+ return false;
+ 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 false;
+ }
+ 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 false;
+ }
+ return true;
+ default:
+ return true;
+ }
+}
+
/* Generate an expression for `FN (ARGS)'. This may change the
contents of ARGS.
@@ -3541,30 +3617,9 @@ 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");
- 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;
- }
- }
+ && fndecl_built_in_p (fn, BUILT_IN_FRONTEND)
+ && !check_frontend_builtin (fn, args, complain))
+ 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-09-08 22:18:23.876079688 +0200
+++ b/gcc/cp/constexpr.cc 2026-09-08 22:26:48.871703070 +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,42 @@ 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 *);
+
+/* 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 modopexpr
+ = build3_loc (loc, MODOP_EXPR, TREE_TYPE (arg), arg, ctor, NULL_TREE);
+ arg = cxx_eval_store_expression (ctx, modopexpr, vc_discard, non_constant_p,
+ overflow_p, jump_target);
+ ggc_free (modopexpr);
+ 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 +2906,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;
}
@@ -3780,14 +3828,13 @@ is_std_allocator (tree ctx)
return is_std_class (ctx, "allocator");
}
-/* Return true if FNDECL is std::allocator<T>::{,de}allocate. */
+/* Return true if FNDECL is std::allocator<T>::allocate. */
static bool
is_std_allocator_allocate (tree fndecl)
{
tree name = DECL_NAME (fndecl);
- if (name == NULL_TREE
- || !(id_equal (name, "allocate") || id_equal (name, "deallocate")))
+ if (name == NULL_TREE || !id_equal (name, "allocate"))
return false;
return is_std_allocator (DECL_CONTEXT (fndecl));
@@ -3803,6 +3850,29 @@ is_std_allocator_allocate (const constex
&& is_std_allocator_allocate (call->fundef->decl));
}
+/* Return true if FNDECL is std::allocator<T>::{,de}allocate. */
+
+static bool
+is_std_allocator_allocate_deallocate (tree fndecl)
+{
+ tree name = DECL_NAME (fndecl);
+ if (name == NULL_TREE
+ || !(id_equal (name, "allocate") || id_equal (name, "deallocate")))
+ return false;
+
+ return is_std_allocator (DECL_CONTEXT (fndecl));
+}
+
+/* Overload for the above taking constexpr_call*. */
+
+static inline bool
+is_std_allocator_allocate_deallocate (const constexpr_call *call)
+{
+ return (call
+ && call->fundef
+ && is_std_allocator_allocate_deallocate (call->fundef->decl));
+}
+
/* Return true if FNDECL is std::source_location::current. */
static inline bool
@@ -4433,7 +4503,7 @@ cxx_eval_call_expression (const constexp
if (TREE_CODE (t) == CALL_EXPR
&& cxx_replaceable_global_alloc_fn (fun)
&& (CALL_FROM_NEW_OR_DELETE_P (t)
- || is_std_allocator_allocate (ctx->call)))
+ || is_std_allocator_allocate_deallocate (ctx->call)))
{
const bool new_op_p = IDENTIFIER_NEW_OP_P (DECL_NAME (fun));
const int nargs = call_expr_nargs (t);
@@ -5107,10 +5177,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 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 +5207,12 @@ 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
+ && cxx_dialect >= cxx26
+ && 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. */
@@ -5143,7 +5221,8 @@ reduced_constant_expression_p (tree t, t
tree cursor = min;
for (auto &e: CONSTRUCTOR_ELTS (t))
{
- if (!reduced_constant_expression_p (e.value))
+ if (!reduced_constant_expression_p
+ (e.value, NULL_TREE, union_elemental_subobject))
return false;
if (array_index_cmp (cursor, e.index) != 0)
return false;
@@ -5168,12 +5247,22 @@ 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 = true;
}
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 (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 +5272,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. */
@@ -8186,7 +8276,7 @@ static bool
modifying_const_object_p (tree_code code, tree obj, bool mutable_p)
{
/* If this is initialization, there's no problem. */
- if (code != MODIFY_EXPR)
+ if (code == INIT_EXPR)
return false;
/* [basic.type.qualifier] "A const object is an object of type
@@ -8213,7 +8303,9 @@ modifying_const_object_p (tree_code code
return false;
}
-/* Evaluate an INIT_EXPR or MODIFY_EXPR. */
+/* Evaluate an INIT_EXPR or MODIFY_EXPR or MODOP_EXPR. MODOP_EXPR means
+ we are evaluating __builtin_start_lifetime (init is in the second
+ operand rather than third, like for INIT_EXPR and MODIFY_EXPR). */
static tree
cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
@@ -8231,8 +8323,8 @@ cxx_eval_store_expression (const constex
maybe_simplify_trivial_copy (target, init);
tree type = TREE_TYPE (target);
- bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) == MODIFY_EXPR;
- if (preeval && !TREE_CLOBBER_P (init))
+ bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) != INIT_EXPR;
+ if (preeval && !TREE_CLOBBER_P (init) && TREE_CODE (t) != MODOP_EXPR)
{
/* Ignore var = .DEFERRED_INIT (); for now, until PR121965 is fixed. */
if (flag_auto_var_init > AUTO_INIT_UNINITIALIZED
@@ -8573,12 +8665,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) != INIT_EXPR
+ && CLASS_TYPE_P (inner)
+ && !type_has_non_deleted_trivial_default_ctor (inner)))
+ && TREE_CODE (t) != MODOP_EXPR)
{
/* Diagnose changing active union member after initialization
without a valid member access expression, as described in
@@ -8675,6 +8768,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 (TREE_CODE (t) == MODOP_EXPR && *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
@@ -10565,7 +10663,7 @@ cxx_eval_constant_expression (const cons
std::source_location::current, we permit casting from void*
because that is compiler-generated code. */
&& !is_std_construct_at (ctx->call)
- && !is_std_allocator_allocate (ctx->call)
+ && !is_std_allocator_allocate_deallocate (ctx->call)
&& !is_std_source_location_current (ctx->call))
{
/* Likewise, don't error when casting from void* when OP is
@@ -10730,6 +10828,16 @@ 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))
+ {
+ /* [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
@@ -12475,7 +12583,7 @@ potential_constant_expression_1 (tree t,
|| TREE_CODE (t) != CALL_EXPR
|| (!CALL_FROM_NEW_OR_DELETE_P (t)
&& (current_function_decl == NULL_TREE
- || !is_std_allocator_allocate
+ || !is_std_allocator_allocate_deallocate
(current_function_decl))))
/* Allow placement new in std::construct_at. */
&& (!cxx_placement_new_fn (fun)
--- a/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C 2026-09-08 12:51:58.346350936 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/feat-cxx26.C 2026-09-08 22:19:17.810415171 +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-08 22:19:17.810568206 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union3.C 2026-09-08 22:19:17.810568206 +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-08 22:19:17.810677075 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union4.C 2026-09-08 22:19:17.810677075 +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-08 22:19:17.810786513 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union5.C 2026-09-08 22:19:17.810786513 +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-08 22:19:17.810884355 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/trivial-union6.C 2026-09-08 22:19:17.810884355 +0200
@@ -0,0 +1,87 @@
+// 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; }
+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 (bool x)
+{
+ C a;
+ __builtin_start_lifetime (&a.b);
+ __builtin_start_lifetime (&a.b[0]);
+ if (x)
+ __builtin_start_lifetime (&a.b[1]);
+ a.b[0][0] = 1;
+ a.b[1][0] = 2;
+ a.b[1][1] = 3;
+ return a;
+}
+constexpr C c2 = fC2 (false); // { dg-error "is not a constant expression" "" { target c++23_down } }
+constexpr C c3 = fC2 (true); // { 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 (bool x)
+{
+ E a;
+ __builtin_start_lifetime (&a.a.b);
+ __builtin_start_lifetime (&a.a.b[0]);
+ if (x)
+ __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 (false); // { dg-error "is not a constant expression" "" { target c++23_down } }
+constexpr E e3 = fE2 (true); // { dg-error "is not a constant expression" "" { target c++23_down } }
+constexpr E
+fE3 (bool x)
+{
+ E a;
+ __builtin_start_lifetime (&a.a.b[0]);
+ 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 } }
--- a/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C 2026-09-08 12:51:58.346350936 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C 2026-09-08 22:19:17.811010696 +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/cpp26/within-lifetime8.C 2026-09-08 22:19:17.811118455 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime8.C 2026-09-08 22:19:17.811118455 +0200
@@ -0,0 +1,167 @@
+// P3450R1 - Extend std::is_within_lifetime
+// { dg-do compile { target c++20 } }
+
+namespace std {
+ template <class T>
+ consteval bool
+ is_within_lifetime (const T *p) noexcept
+ {
+ return __builtin_is_within_lifetime (p);
+ }
+
+ template <class T>
+ constexpr void
+ start_lifetime (T &r) noexcept
+ {
+ __builtin_start_lifetime (__builtin_addressof (r));
+ }
+}
+
+struct A { int a, b; };
+struct B { A c, d[2]; };
+union C { B e; int f; };
+struct D { C g; B h; };
+struct E { D i[3]; };
+union F { E j; int k; };
+
+consteval int
+foo ()
+{
+ F l;
+ l.j.i[0].h.c.a = 1;
+ if (!std::is_within_lifetime (&l.j.i[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].h))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+ return __LINE__;
+ l.k = 1;
+ if (std::is_within_lifetime (&l.j.i[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].h))
+ return __LINE__;
+ 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[1]);
+ if (!std::is_within_lifetime (&l.j.i[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].h))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+ return __LINE__;
+ std::start_lifetime (l.j.i[1].g);
+ if (!std::is_within_lifetime (&l.j.i[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].h))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+ return __LINE__;
+ std::start_lifetime (l.j.i[1].h);
+ if (!std::is_within_lifetime (&l.j.i[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].h))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+ return __LINE__;
+ std::start_lifetime (l.j.i[1].h.d);
+ std::start_lifetime (l.j.i[1].h.d[0]);
+ if (std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+ return __LINE__;
+ l.j.i[1].h.d[0].a = 42;
+ if (!std::is_within_lifetime (&l.j.i[1].h))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+ return __LINE__;
+ std::start_lifetime (l.j.i[1].g.e);
+ std::start_lifetime (l.j.i[1].g.e.d);
+ std::start_lifetime (l.j.i[1].g.e.d[1]);
+ if (!std::is_within_lifetime (&l.j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.f))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+ return __LINE__;
+ l.j.i[1].g.e.d[1].b = 42;
+ if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+ return __LINE__;
+ std::start_lifetime (l.j.i[1].g);
+ if (!std::is_within_lifetime (&l.j.i[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].g))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].g.e))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.f))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].h))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+ return __LINE__;
+ std::start_lifetime (l.j.i[1].g);
+ l.j.i[1].g.f = 42;
+ if (!std::is_within_lifetime (&l.j.i[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].g))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].g.f))
+ return __LINE__;
+ if (std::is_within_lifetime (&l.j.i[1].g.e.d[1].b))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].h))
+ return __LINE__;
+ if (!std::is_within_lifetime (&l.j.i[1].h.d[0].a))
+ return __LINE__;
+ l.k = 1;
+ return 0;
+}
+
+static_assert (foo () == 0);
--- a/gcc/testsuite/g++.dg/cpp26/within-lifetime9.C 2026-09-08 22:19:17.811243333 +0200
+++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime9.C 2026-09-08 22:19:17.811243333 +0200
@@ -0,0 +1,104 @@
+// P3450R1 - Extend std::is_within_lifetime
+// { dg-do compile { target c++20 } }
+
+namespace std {
+ template <class T>
+ consteval bool
+ is_within_lifetime (const T *p) noexcept
+ {
+ return __builtin_is_within_lifetime (p);
+ }
+
+ template <class T>
+ constexpr void
+ start_lifetime (T &r) noexcept
+ {
+ __builtin_start_lifetime (__builtin_addressof (r));
+ }
+}
+
+struct A {};
+struct B : A {};
+struct C { B a; };
+union D { A a[3]; B b[3]; C c[3]; };
+
+consteval int
+foo ()
+{
+ union D d;
+ if (std::is_within_lifetime (&d.a[0]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.b[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.c[2]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.c[2].a))
+ return __LINE__;
+ std::start_lifetime (d.a);
+ std::start_lifetime (d.a[0]);
+ if (!std::is_within_lifetime (&d.a[0]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.b[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.c[2]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.c[2].a))
+ return __LINE__;
+ std::start_lifetime (d.b);
+ std::start_lifetime (d.b[1]);
+ if (std::is_within_lifetime (&d.a[0]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&d.b[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.c[2]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.c[2].a))
+ return __LINE__;
+ std::start_lifetime (d.c);
+ std::start_lifetime (d.c[2]);
+ if (std::is_within_lifetime (&d.a[0]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.b[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&d.c[2]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.c[2].a))
+ return __LINE__;
+ std::start_lifetime (d.c[2].a);
+ if (std::is_within_lifetime (&d.a[0]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.b[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&d.c[2]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&d.c[2].a))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.c[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.c[1].a))
+ return __LINE__;
+ std::start_lifetime (d.c[1]);
+ if (!std::is_within_lifetime (&d.c[1]))
+ return __LINE__;
+ if (std::is_within_lifetime (&d.c[1].a))
+ return __LINE__;
+ return 0;
+}
+
+static_assert (foo () == 0);
+
+consteval int
+bar ()
+{
+ union D d;
+ std::start_lifetime (d.c);
+ std::start_lifetime (d.c[1]);
+ d.c[1].a = {};
+ if (!std::is_within_lifetime (&d.c[1]))
+ return __LINE__;
+ if (!std::is_within_lifetime (&d.c[1].a))
+ return __LINE__;
+ return 0;
+}
+
+static_assert (bar () == 0); // { dg-bogus "static assertion failed" "" { xfail *-*-* } }
--- a/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C 2026-09-08 12:51:58.346350936 +0200
+++ b/gcc/testsuite/g++.dg/cpp29/feat-cxx29.C 2026-09-08 22:19:17.811424698 +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-08 12:51:58.350350884 +0200
+++ b/libstdc++-v3/include/bits/version.def 2026-09-08 22:19:17.811646590 +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-08 12:51:58.350350884 +0200
+++ b/libstdc++-v3/include/bits/version.h 2026-09-08 22:19:17.811923839 +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-08 12:51:58.349350897 +0200
+++ b/libstdc++-v3/include/bits/stl_construct.h 2026-09-08 22:19:17.812189566 +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-08 12:51:58.350350884 +0200
+++ b/libstdc++-v3/include/std/memory 2026-09-08 22:19:17.812354643 +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-08 12:51:58.350350884 +0200
+++ b/libstdc++-v3/src/c++23/std.cc.in 2026-09-08 22:19:17.812604195 +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
More information about the Libstdc++
mailing list