https://gcc.gnu.org/g:eccc490c21fc7c9dadf8d13566963bce151c902d
commit r17-3846-geccc490c21fc7c9dadf8d13566963bce151c902d Author: Jakub Jelinek <[email protected]> Date: Wed Sep 2 10:07:18 2026 +0200 c++, libstdc++: Implement C++26 P2641R4 - Checking if a union alternative is active The following patch attempts to implement this paper (though for now just using what the FE provides instead of introducing new stuff, see below). There is a new builtin which returns true if what the argument points to is within lifetime, false if it is not and results in non-constant expression if it points to something non-accessible in constant expression, or the complete object is not in lifetime. Most of the tests work identically in clang++ which implements this paper for some time and g++ with this patch. There are a few differences/problems, some of those I'd like to address incrementally: 1) this patch implements just P2641R4, not the subsequent P3450R1 paper. The plan is to use template<class _Up = void, typename _Tp> consteval bool is_within_lifetime(const _Tp* __p) noexcept { return __builtin_is_within_lifetime (__p) && __builtin_constant_p (static_cast <const volatile _Up *> (__p) && true); } afterwards but we need https://gcc.gnu.org/pipermail/gcc-patches/2026-August/thread.html#726464 finished for that; I'll restart work on that soon 2) we don't implement std::allocator<T>::allocate correctly at constant evaluation time, in particular https://eel.is/c++draft/memory#allocator.members-5.sentence-2 That is pretty much the same thing as in P3726R2 std::start_lifetime does, therefore my current plan is once this is in, to restart working on P3726R2 and add a new CONSTRUCTOR bit next to CONSTRUCTOR_NO_CLEARING which would mean the left out elements are not in lifetime even when the whole CONSTRUCTOR is, add support for that in cxx_eval_is_within_lifetime and implicitly pretend the builtin is used in std::allocator<T>::allocate; this is covered in the testsuite in within-lifetime3.C but is guarded with #if 0 3) the standard says in https://eel.is/c++draft/type.traits#meta.const.eval-5 that "p points to an object that is usable in constant expressions or whose complete object's lifetime began within E". This patch assumes Jason's proposed change to this sentence to change lifetime to initialization. 4) for complete objects created during E evaluation, the patch uses a new get_raw_value_ptr method (the 2 argument one is too much store specific, but I can't just use get_value, because I need to differentiate between values hash map doesn't have an entry for this var yet, it clearly has not started lifetime yet, and values hash map has a NULL_TREE entry for that, that e.g. for scalar means it has started lifetime but is uninitialized) and heap_deleted_identifier (for use after delete) and is_outside_lifetime (which is use after destruction) and also needs to differentiate between the case where pointer points to a complete object after its storage has been released (that is an error/non-constant) vs. when say std::destroy_at has been called on it (it is outside of lifetime, but should just return false in that case) 5) we don't handle the builtin correctly inside of constructors where we need to keep track of what mem-initializers have been processed already (and thus those members are within lifetime) and what have not yet 6) I fear there might be issues with calling is_within_lifetime on pointers to empty class subobjects, I think e.g. reduced_constant_expression_p actively removes initializers for those In any case, despite the above limitations I think this patch works for most of the intended uses of the new function template and I hope the rest can be addressed incrementally. 2026-09-02 Jakub Jelinek <[email protected]> P2641R4 gcc/cp/ * cp-tree.h: Implement C++26 P2641R4 - Checking if a union alternative is active. (enum cp_built_in_function): Add CP_BUILT_IN_IS_WITHIN_LIFETIME. * tree.cc (builtin_valid_in_constant_expr_p): Handle CP_BUILT_IN_IS_WITHIN_LIFETIME. * decl.cc (cxx_init_decl_processing): Create decl for CP_BUILT_IN_IS_WITHIN_LIFETIME. * semantics.cc (finish_call_expr): Perform semantic checking of __builtin_is_within_lifetime. * constexpr.cc (constexpr_global_ctx::get_value): New method. (eval_and_check_array_index): Add forward declaration. (cxx_eval_is_within_lifetime): New function. (cxx_eval_builtin_function_call): Handle CP_BUILT_IN_IS_WITHIN_LIFETIME. gcc/testsuite/ * g++.dg/cpp26/within-lifetime1.C: New test. * g++.dg/cpp26/within-lifetime2.C: New test. * g++.dg/cpp26/within-lifetime3.C: New test. * g++.dg/cpp26/within-lifetime4.C: New test. * g++.dg/cpp26/within-lifetime5.C: New test. * g++.dg/cpp26/within-lifetime6.C: New test. * g++.dg/cpp26/within-lifetime7.C: New test. libstdc++-v3/ * include/bits/version.def (within_lifetime): New. * include/bits/version.h: Regenerate. * include/std/type_traits: Define __glibcxx_want_within_lifetime before including bits/version.h. (std::is_within_lifetime): New function template. * src/c++23/std.cc.in (std::is_within_lifetime): Export. Reviewed-by: Jason Merrill <[email protected]> Diff: --- gcc/cp/constexpr.cc | 273 +++++++++++++++++++- gcc/cp/cp-tree.h | 1 + gcc/cp/decl.cc | 6 + gcc/cp/semantics.cc | 26 ++ gcc/cp/tree.cc | 1 + gcc/testsuite/g++.dg/cpp26/within-lifetime1.C | 353 ++++++++++++++++++++++++++ gcc/testsuite/g++.dg/cpp26/within-lifetime2.C | 65 +++++ gcc/testsuite/g++.dg/cpp26/within-lifetime3.C | 244 ++++++++++++++++++ gcc/testsuite/g++.dg/cpp26/within-lifetime4.C | 157 ++++++++++++ gcc/testsuite/g++.dg/cpp26/within-lifetime5.C | 46 ++++ gcc/testsuite/g++.dg/cpp26/within-lifetime6.C | 41 +++ gcc/testsuite/g++.dg/cpp26/within-lifetime7.C | 75 ++++++ libstdc++-v3/include/bits/version.def | 9 + libstdc++-v3/include/bits/version.h | 10 + libstdc++-v3/include/std/type_traits | 8 + libstdc++-v3/src/c++23/std.cc.in | 3 + 16 files changed, 1310 insertions(+), 8 deletions(-) diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index dbfc460d04f3..a78150786c9a 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -1180,8 +1180,9 @@ enum constexpr_switch_state { class constexpr_global_ctx { /* Values for any temporaries or local variables within the - constant-expression. Objects outside their lifetime have - value 'void_node'. */ + constant-expression. Objects outside their lifetime have + value 'void_node' or 'void_list_node', the former if they are outside + of lifetime but still before storage has been deallocated. */ hash_map<tree,tree> values; public: /* Number of cxx_eval_constant_expression calls (except skipped ones, @@ -1242,24 +1243,28 @@ public: bool is_outside_lifetime (tree t) { if (tree *p = values.get (t)) - if (*p == void_node) + if (*p == void_node || *p == void_list_node) return true; return false; } tree get_value (tree t) { if (tree *p = values.get (t)) - if (*p != void_node) + if (*p != void_node && *p != void_list_node) return *p; return NULL_TREE; } + tree *get_raw_value_ptr (tree t) + { + return values.get (t); + } tree *get_value_ptr (tree t, bool initializing) { if (modifiable && !modifiable->contains (t)) return nullptr; if (tree *p = values.get (t)) { - if (*p != void_node) + if (*p != void_node && *p != void_list_node) return p; else if (initializing) { @@ -1275,12 +1280,12 @@ public: if (!already_in_map && modifiable) modifiable->add (t); } - void destroy_value (tree t) + void destroy_value (tree t, bool past_storage_end = true) { if (TREE_CODE (t) == VAR_DECL || TREE_CODE (t) == PARM_DECL || TREE_CODE (t) == RESULT_DECL) - values.put (t, void_node); + values.put (t, past_storage_end ? void_list_node : void_node); else values.remove (t); } @@ -2533,6 +2538,253 @@ cxx_eval_constexpr_diag (const constexpr_ctx *ctx, tree t, bool *non_constant_p, return void_node; } +static tree eval_and_check_array_index (const constexpr_ctx *, tree, bool, + bool *, bool *, tree *); + +/* Attempt to evaluate T which represents a call to + __builtin_is_within_lifetime. */ + +static tree +cxx_eval_is_within_lifetime (const constexpr_ctx *ctx, tree t, + bool *non_constant_p, bool *overflow_p, + tree *jump_target) +{ + location_t loc = EXPR_LOCATION (t); + tree arg = CALL_EXPR_ARG (t, 0); + arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue, + non_constant_p, overflow_p, + jump_target); + if (*jump_target) + return NULL_TREE; + if (*non_constant_p) + return t; + /* Need to strip casts to pointers to void, but should preserve + pointer casts within the innermost pointer to void cast if + any, so that e.g. pointers to heap allocations are handled + correctly. */ + tree p = arg; + while (CONVERT_EXPR_P (p) || TREE_CODE (p) == NON_LVALUE_EXPR) + { + if (!TYPE_PTR_P (TREE_TYPE (p))) + break; + if (VOID_TYPE_P (TREE_TYPE (TREE_TYPE (p)))) + arg = TREE_OPERAND (p, 0); + p = TREE_OPERAND (p, 0); + } + if (integer_zerop (arg)) + { + if (!ctx->quiet) + error_at (loc, "%qs called with a null pointer", + "__builtin_is_within_lifetime"); + *non_constant_p = true; + return t; + } + if (POINTER_TYPE_P (TREE_TYPE (arg)) + && (TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == FUNCTION_TYPE + || TREE_CODE (TREE_TYPE (TREE_TYPE (arg))) == METHOD_TYPE)) + { + if (!ctx->quiet) + error_at (loc, "%qs called with pointer to function", + "__builtin_is_within_lifetime"); + *non_constant_p = true; + return t; + } + arg = build1 (INDIRECT_REF, TREE_TYPE (TREE_TYPE (arg)), arg); + arg = cxx_eval_constant_expression (ctx, arg, vc_glvalue, + non_constant_p, overflow_p, + jump_target); + if (*jump_target) + return NULL_TREE; + if (*non_constant_p) + return t; + auto_vec <tree, 4> refs; + tree obj; + for (obj = arg; ; obj = TREE_OPERAND (obj, 0)) + { + switch (TREE_CODE (obj)) + { + case COMPONENT_REF: + case ARRAY_REF: + case REALPART_EXPR: + case IMAGPART_EXPR: + refs.safe_push (obj); + continue; + default: + break; + } + break; + } + tree *valp = nullptr; + bool check_mutable = false; + if (DECL_P (obj)) + { + valp = ctx->global->get_raw_value_ptr (obj); + if (!valp + && VAR_P (obj) + && TREE_STATIC (obj) + && decl_constant_var_p (obj)) + { + valp = &DECL_INITIAL (obj); + check_mutable = true; + } + } + if (!valp || *valp == void_list_node) + { + if (!ctx->quiet) + { + auto_diagnostic_group d; + if (DECL_P (obj) && DECL_NAME (obj) == heap_deleted_identifier) + { + error_at (loc, "%qs on allocated storage after deallocation " + "is not a constant expression", + "__builtin_is_within_lifetime"); + inform (DECL_SOURCE_LOCATION (obj), "allocated here"); + } + else if (DECL_P (obj) && ctx->global->is_outside_lifetime (obj)) + { + error_at (loc, "%qs on %qE after its storage has been released " + "is not a constant expression", + "__builtin_is_within_lifetime", obj); + inform (DECL_SOURCE_LOCATION (obj), "declared here"); + } + else + error_at (loc, "%qs on %qE from outside current evaluation " + "is not a constant expression", + "__builtin_is_within_lifetime", obj); + } + *non_constant_p = true; + return t; + } + tree val = *valp; + if (val == void_node) + return boolean_false_node; + unsigned i; + tree ref; + /* Magic value used for val when inside the following loop when + inside of an omitted part of initializer due to it being + uninitialized. In this case return boolean_true_node unless + there is some union member access, in unitialized union + no member is within lifetime. */ + const tree val_uninit = global_namespace; + /* Magic value used for val when inside the following loop when + inside of an omitted part of initializer due to it being + zero initialized. In this case return boolean_true_node unless + there is some union member access except for the first member. */ + const tree val_zero_init = std_node; + if (val == NULL_TREE) + val = val_uninit; + FOR_EACH_VEC_ELT_REVERSE (refs, i, ref) + switch (TREE_CODE (ref)) + { + case REALPART_EXPR: + case IMAGPART_EXPR: + if (TREE_CODE (val) == COMPLEX_EXPR + && (TREE_OPERAND (val, TREE_CODE (ref) == IMAGPART_EXPR) + == void_node)) + return boolean_false_node; + return boolean_true_node; + case COMPONENT_REF: + if (check_mutable && DECL_MUTABLE_P (TREE_OPERAND (ref, 1))) + { + if (!ctx->quiet) + error_at (loc, "%qs on %<mutable%> sub-object %qD", + "__builtin_is_within_lifetime", + TREE_OPERAND (ref, 1)); + *non_constant_p = true; + return t; + } + if (TREE_CODE (TREE_TYPE (TREE_OPERAND (ref, 0))) == UNION_TYPE) + { + tree union_type = TREE_TYPE (TREE_OPERAND (ref, 0)); + if (val == val_zero_init) + { + if (TREE_OPERAND (ref, 1) + != next_aggregate_field (TYPE_FIELDS (union_type))) + return boolean_false_node; + continue; + } + else if (val == val_uninit) + return boolean_false_node; + else + { + gcc_assert (TREE_CODE (val) == CONSTRUCTOR); + if (CONSTRUCTOR_NELTS (val) == 0) + { + if (CONSTRUCTOR_NO_CLEARING (val)) + return boolean_false_node; + tree first + = next_aggregate_field (TYPE_FIELDS (union_type)); + if (first != TREE_OPERAND (ref, 1)) + return boolean_false_node; + val = val_zero_init; + continue; + } + else + { + if (CONSTRUCTOR_ELT (val, 0)->index + != TREE_OPERAND (ref, 1)) + return boolean_false_node; + val = CONSTRUCTOR_ELT (val, 0)->value; + if (val == void_node) + return boolean_false_node; + continue; + } + } + } + if (val == val_zero_init || val == val_uninit) + continue; + gcc_assert (TREE_CODE (val) == CONSTRUCTOR); + unsigned int j; + tree field, value; + FOR_EACH_CONSTRUCTOR_ELT (CONSTRUCTOR_ELTS (val), j, field, value) + if (field == TREE_OPERAND (ref, 1)) + { + if (value == void_node) + return boolean_false_node; + val = value; + ref = NULL_TREE; + break; + } + if (ref == NULL_TREE) + continue; + if (CONSTRUCTOR_NO_CLEARING (val)) + val = val_uninit; + else + val = val_zero_init; + continue; + case ARRAY_REF: + field = eval_and_check_array_index (ctx, ref, false, + non_constant_p, overflow_p, + jump_target); + if (*jump_target) + return NULL_TREE; + if (*non_constant_p) + return t; + if (val == val_zero_init || val == val_uninit) + continue; + if (TREE_CODE (val) == STRING_CST) + return boolean_true_node; + gcc_assert (TREE_CODE (val) == CONSTRUCTOR); + HOST_WIDE_INT idx; + idx = find_array_ctor_elt (val, field, false); + if (idx != -1) + { + val = CONSTRUCTOR_ELT (val, idx)->value; + if (val == void_node) + return boolean_false_node; + continue; + } + if (CONSTRUCTOR_NO_CLEARING (val)) + val = val_uninit; + else + val = val_zero_init; + continue; + default: + gcc_unreachable (); + } + return boolean_true_node; +} + /* Attempt to evaluate T which represents a call to a builtin function. We assume here that all builtin functions evaluate to scalar types represented by _CST nodes. */ @@ -2606,6 +2858,10 @@ cxx_eval_builtin_function_call (const constexpr_ctx *ctx, tree t, tree fun, return cxx_eval_constexpr_diag (ctx, t, non_constant_p, overflow_p, jump_target); + case CP_BUILT_IN_IS_WITHIN_LIFETIME: + return cxx_eval_is_within_lifetime (ctx, t, non_constant_p, overflow_p, + jump_target); + default: break; } @@ -8171,7 +8427,8 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t, if (CLOBBER_KIND (init) >= CLOBBER_OBJECT_END && refs->is_empty ()) { - ctx->global->destroy_value (object); + ctx->global->destroy_value (object, (CLOBBER_KIND (init) + > CLOBBER_OBJECT_END)); return void_node; } diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 75c00f88a083..6b9c8c1ba3f5 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -7211,6 +7211,7 @@ enum cp_built_in_function { CP_BUILT_IN_CONSTEXPR_DIAG, CP_BUILT_IN_CURRENT_EXCEPTION, CP_BUILT_IN_UNCAUGHT_EXCEPTIONS, + CP_BUILT_IN_IS_WITHIN_LIFETIME, CP_BUILT_IN_LAST }; diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index 55badc9eed97..27183cd98852 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -5673,6 +5673,12 @@ cxx_init_decl_processing (void) BUILT_IN_FRONTEND, NULL, NULL_TREE); set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF); + decl = add_builtin_function ("__builtin_is_within_lifetime", + bool_vaftype, CP_BUILT_IN_IS_WITHIN_LIFETIME, + BUILT_IN_FRONTEND, NULL, NULL_TREE); + set_call_expr_flags (decl, ECF_NOTHROW | ECF_LEAF); + SET_DECL_IMMEDIATE_FUNCTION_P (decl); + integer_two_node = build_int_cst (NULL_TREE, 2); /* Guess at the initial static decls size. */ diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 7907668da36e..469f71dcab6d 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -3540,6 +3540,32 @@ finish_call_expr (tree fn, vec<tree, va_gc> **args, bool disallow_virtual, warn_for_memset (input_location, arg0, arg2, literal_mask); } + if (TREE_CODE (fn) == FUNCTION_DECL + && fndecl_built_in_p (fn, CP_BUILT_IN_IS_WITHIN_LIFETIME, + BUILT_IN_FRONTEND)) + { + /* Unless users call the builtin directly, the following 2 checks + should be ensured from std::is_within_lifetime template. */ + if (vec_safe_length (*args) != 1) + { + if (complain & tf_error) + error ("%<__builtin_is_within_lifetime%> needs a single " + "argument"); + return error_mark_node; + } + tree arg = (**args)[0]; + if (error_operand_p (arg)) + return error_mark_node; + tree ptype = TREE_TYPE (arg); + if (!POINTER_TYPE_P (ptype)) + { + if (complain & tf_error) + error ("%<__builtin_is_within_lifetime%> argument type " + "%qT is not pointer type", ptype); + return error_mark_node; + } + } + /* A call to a namespace-scope function. */ result = build_new_function_call (fn, args, orig_complain); } diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index a4aec230a340..1d88d6c574ba 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -578,6 +578,7 @@ builtin_valid_in_constant_expr_p (const_tree decl) case CP_BUILT_IN_CONSTEXPR_DIAG: case CP_BUILT_IN_CURRENT_EXCEPTION: case CP_BUILT_IN_UNCAUGHT_EXCEPTIONS: + case CP_BUILT_IN_IS_WITHIN_LIFETIME: return true; default: break; diff --git a/gcc/testsuite/g++.dg/cpp26/within-lifetime1.C b/gcc/testsuite/g++.dg/cpp26/within-lifetime1.C new file mode 100644 index 000000000000..e5ce7bdb830c --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime1.C @@ -0,0 +1,353 @@ +// P3450R1 - Extend std::is_within_lifetime +// { dg-do compile { target c++20 } } + +#if __has_builtin(__builtin_is_within_lifetime) +namespace std { + template <class T> + consteval bool + is_within_lifetime (const T *p) noexcept + { + return __builtin_is_within_lifetime (p); + } +} +#endif + +namespace std { + template <typename T, typename F> + constexpr T + bit_cast (const F &f) noexcept + { + return __builtin_bit_cast (T, f); + } +} + +constexpr char d = 0; +struct E { char a; union F { int b; long c; short d; struct G { int e; } f[2]; } g; }; +constexpr E e = {}; +constexpr E f = { .a = 1, .g = { .f = {} } }; +constexpr int s = 42; +constexpr int t[2] = {}; + +consteval int +foo () +{ + char a = 0; + struct B { int b; } b = {}; + struct C : B { int c[2]; union D { int d; long e; } f; } c = {}; + union H { union I { int a; union J { int b; long c; } d; } e; long f; } g = {}; + H h; + C i; + char j; + bool k = true; + if (!std::is_within_lifetime (&a)) + return __LINE__; + if (!std::is_within_lifetime (&b)) + return __LINE__; + if (!std::is_within_lifetime (&b.b)) + return __LINE__; + if (!std::is_within_lifetime (&c)) + return __LINE__; + if (!std::is_within_lifetime (&c.b)) + return __LINE__; + if (!std::is_within_lifetime (&c.c)) + return __LINE__; + if (!std::is_within_lifetime (&c.c[1])) + return __LINE__; + if (!std::is_within_lifetime (&c.f)) + return __LINE__; + if (!std::is_within_lifetime (&c.f.d)) + return __LINE__; + if (std::is_within_lifetime (&c.f.e)) + return __LINE__; + if (!std::is_within_lifetime (&d)) + return __LINE__; + if (!std::is_within_lifetime (&e)) + return __LINE__; + if (!std::is_within_lifetime (&e.a)) + return __LINE__; + if (!std::is_within_lifetime (&e.g)) + return __LINE__; + if (!std::is_within_lifetime (&e.g.b)) + return __LINE__; + if (std::is_within_lifetime (&e.g.c)) + return __LINE__; + if (std::is_within_lifetime (&e.g.d)) + return __LINE__; + if (std::is_within_lifetime (&e.g.f)) + return __LINE__; + if (!std::is_within_lifetime (&f)) + return __LINE__; + if (!std::is_within_lifetime (&f.a)) + return __LINE__; + if (!std::is_within_lifetime (&f.g)) + return __LINE__; + if (std::is_within_lifetime (&f.g.b)) + return __LINE__; + if (std::is_within_lifetime (&f.g.c)) + return __LINE__; + if (std::is_within_lifetime (&f.g.d)) + return __LINE__; + if (!std::is_within_lifetime (&f.g.f)) + return __LINE__; + if (!std::is_within_lifetime (&f.g.f[0])) + return __LINE__; + if (!std::is_within_lifetime (&f.g.f[0].e)) + return __LINE__; + if (!std::is_within_lifetime (&f.g.f[1].e)) + return __LINE__; + if (!std::is_within_lifetime (&g)) + return __LINE__; + if (!std::is_within_lifetime (&g.e)) + return __LINE__; + if (std::is_within_lifetime (&g.f)) + return __LINE__; + if (!std::is_within_lifetime (&g.e.a)) + return __LINE__; + if (std::is_within_lifetime (&g.e.d)) + return __LINE__; + g.e.d.c = 1; + if (!std::is_within_lifetime (&g)) + return __LINE__; + if (!std::is_within_lifetime (&g.e)) + return __LINE__; + if (std::is_within_lifetime (&g.f)) + return __LINE__; + if (std::is_within_lifetime (&g.e.a)) + return __LINE__; + if (!std::is_within_lifetime (&g.e.d)) + return __LINE__; + if (std::is_within_lifetime (&g.e.d.b)) + return __LINE__; + if (!std::is_within_lifetime (&g.e.d.c)) + return __LINE__; + g.f = 42; + if (!std::is_within_lifetime (&g)) + return __LINE__; + if (std::is_within_lifetime (&g.e)) + return __LINE__; + if (!std::is_within_lifetime (&g.f)) + return __LINE__; + if (!std::is_within_lifetime (&h)) + return __LINE__; + if (std::is_within_lifetime (&h.e)) + return __LINE__; + if (std::is_within_lifetime (&h.f)) + return __LINE__; + if (!std::is_within_lifetime (&i)) + return __LINE__; + if (!std::is_within_lifetime (&i.b)) + return __LINE__; + if (!std::is_within_lifetime (&i.c[1])) + return __LINE__; + if (!std::is_within_lifetime (&i.f)) + return __LINE__; + if (std::is_within_lifetime (&i.f.d)) + return __LINE__; + if (std::is_within_lifetime (&i.f.e)) + return __LINE__; + if (!std::is_within_lifetime (&j)) + return __LINE__; + if (!std::is_within_lifetime (&k)) + return __LINE__; + unsigned char l = std::bit_cast <unsigned char> (k); + if (!std::is_within_lifetime (&l)) + return __LINE__; + struct {} m; + if (!std::is_within_lifetime (&m)) + return __LINE__; + unsigned char n = std::bit_cast <unsigned char> (m); + if (!std::is_within_lifetime (&n)) + return __LINE__; + int o; + if (!std::is_within_lifetime (static_cast <volatile int *> (&o))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&o))) + return __LINE__; + volatile int p; + if (!std::is_within_lifetime (const_cast <int *> (&p))) + return __LINE__; + struct { union { union { struct { int a; long b; } c; int d; } e; int f; } g; int h; } q; + if (!std::is_within_lifetime (&q)) + return __LINE__; + if (!std::is_within_lifetime (&q.g)) + return __LINE__; + if (!std::is_within_lifetime (&q.h)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e)) + return __LINE__; + if (std::is_within_lifetime (&q.g.f)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.c)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.d)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.c.a)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.c.b)) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.g))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.h))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.f))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.d))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b))) + return __LINE__; + q.g.f = 0; + if (!std::is_within_lifetime (&q)) + return __LINE__; + if (!std::is_within_lifetime (&q.g)) + return __LINE__; + if (!std::is_within_lifetime (&q.h)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e)) + return __LINE__; + if (!std::is_within_lifetime (&q.g.f)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.c)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.d)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.c.a)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.c.b)) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.g))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.h))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.g.f))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.d))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b))) + return __LINE__; + q.g.e.d = 0; + if (!std::is_within_lifetime (&q)) + return __LINE__; + if (!std::is_within_lifetime (&q.g)) + return __LINE__; + if (!std::is_within_lifetime (&q.h)) + return __LINE__; + if (!std::is_within_lifetime (&q.g.e)) + return __LINE__; + if (std::is_within_lifetime (&q.g.f)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.c)) + return __LINE__; + if (!std::is_within_lifetime (&q.g.e.d)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.c.a)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.c.b)) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.g))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.h))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.f))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.d))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b))) + return __LINE__; + q.g.e.c.a = 0; + if (!std::is_within_lifetime (&q)) + return __LINE__; + if (!std::is_within_lifetime (&q.g)) + return __LINE__; + if (!std::is_within_lifetime (&q.h)) + return __LINE__; + if (!std::is_within_lifetime (&q.g.e)) + return __LINE__; + if (std::is_within_lifetime (&q.g.f)) + return __LINE__; + if (!std::is_within_lifetime (&q.g.e.c)) + return __LINE__; + if (std::is_within_lifetime (&q.g.e.d)) + return __LINE__; + if (!std::is_within_lifetime (&q.g.e.c.a)) + return __LINE__; + if (!std::is_within_lifetime (&q.g.e.c.b)) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.g))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.h))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.f))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.c))) + return __LINE__; + if (std::is_within_lifetime (static_cast <void *> (&q.g.e.d))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.c.a))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <void *> (&q.g.e.c.b))) + return __LINE__; + struct { union { int a; short b; }; mutable int c; } r = { .b = 42 }; + if (!std::is_within_lifetime (&r)) + return __LINE__; + if (std::is_within_lifetime (&r.a)) + return __LINE__; + if (!std::is_within_lifetime (&r.b)) + return __LINE__; + if (!std::is_within_lifetime (&r.c)) + return __LINE__; + if (!std::is_within_lifetime (&s)) + return __LINE__; + if (!std::is_within_lifetime (const_cast <int *> (&s))) + return __LINE__; + if (!std::is_within_lifetime (const_cast <volatile int *> (&s))) + return __LINE__; + if (!std::is_within_lifetime (static_cast <const void *> (&s))) + return __LINE__; + if (!std::is_within_lifetime (t)) + return __LINE__; + if (!std::is_within_lifetime (t + 0)) + return __LINE__; + if (!std::is_within_lifetime (t + 1)) + return __LINE__; + int u[4]; + if (!std::is_within_lifetime (&u)) + return __LINE__; + if (!std::is_within_lifetime (&u[2])) + return __LINE__; + u[1] = 42; + if (!std::is_within_lifetime (&u)) + return __LINE__; + if (!std::is_within_lifetime (&u[2])) + return __LINE__; + return 0; +} + +static_assert (foo () == 0); diff --git a/gcc/testsuite/g++.dg/cpp26/within-lifetime2.C b/gcc/testsuite/g++.dg/cpp26/within-lifetime2.C new file mode 100644 index 000000000000..dc0544848f7f --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime2.C @@ -0,0 +1,65 @@ +// P3450R1 - Extend std::is_within_lifetime +// { dg-do compile { target c++20 } } + +#include "../cpp2a/construct_at.h" + +namespace std { + template <class T> + consteval bool + is_within_lifetime (const T *p) noexcept + { + return __builtin_is_within_lifetime (p); + } +} + +consteval bool +foo (int &x, char &y) +{ + if (std::is_within_lifetime (&x) || std::is_within_lifetime (&y)) + return false; + std::construct_at (&y, 42); + if (std::is_within_lifetime (&x) || !std::is_within_lifetime (&y)) + return false; + std::construct_at (&x, 41); + if (!std::is_within_lifetime (&x) || std::is_within_lifetime (&y)) + return false; + return true; +} + +static_assert ([] { union { int a; char b; } u; return foo (u.a, u.b); } ()); +static_assert ([] { union { int a; char b; }; return foo (a, b); } ()); +static_assert ([] { struct { union { int a; char b; }; } s; return foo (s.a, s.b); } ()); +static_assert ([] { struct { union { int a; char b; } u; } s; return foo (s.u.a, s.u.b); } ()); + +consteval bool +bar () +{ + union { union { int a; long b; } c; short d; }; + if (std::is_within_lifetime (&d) + || std::is_within_lifetime (&c) + || std::is_within_lifetime (&c.a) + || std::is_within_lifetime (&c.b)) + return false; + std::construct_at (&d); + if (!std::is_within_lifetime (&d) + || std::is_within_lifetime (&c) + || std::is_within_lifetime (&c.a) + || std::is_within_lifetime (&c.b)) + return false; + std::construct_at (&c); + std::construct_at (&c.b); + if (std::is_within_lifetime (&d) + || !std::is_within_lifetime (&c) + || std::is_within_lifetime (&c.a) + || !std::is_within_lifetime (&c.b)) + return false; + std::construct_at (&c.a); + if (std::is_within_lifetime (&d) + || !std::is_within_lifetime (&c) + || !std::is_within_lifetime (&c.a) + || std::is_within_lifetime (&c.b)) + return false; + return true; +} + +static_assert (bar ()); diff --git a/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C b/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C new file mode 100644 index 000000000000..416a25438b28 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime3.C @@ -0,0 +1,244 @@ +// P3450R1 - Extend std::is_within_lifetime +// { dg-do compile { target c++20 } } + +#include "../cpp2a/construct_at.h" + +namespace std { + template <class T> + consteval bool + is_within_lifetime (const T *p) noexcept + { + return __builtin_is_within_lifetime (p); + } +} + +consteval bool +foo (bool x) +{ + std::allocator <int> a; + auto b = a.allocate (1); +#if 0 + // [allocator.members]/5 says it should start lifetime of the array + // but not its elements, and b points to the first element. + if (std::is_within_lifetime (b)) + return false; +#endif + std::construct_at (b); + if (!std::is_within_lifetime (b)) + return false; + std::destroy_at (b); + if (std::is_within_lifetime (b)) + return false; + a.deallocate (b, 1); + if (x) + __builtin_is_within_lifetime (b); // { dg-error "'__builtin_is_within_lifetime' on allocated storage after deallocation is not a constant expression" } + return true; +} + +static_assert (foo (false)); +bool a = foo (true); // { dg-error "call to consteval function 'foo\\\(true\\\)' is not a constant expression" } + +consteval bool +bar (int x) +{ + int *a; + decltype (nullptr) *b; + { + int c = 42; + a = &c; + if (!std::is_within_lifetime (a)) + return false; + } + { + int c = 42; + if (x == 1) + __builtin_is_within_lifetime (a); // { dg-error "'__builtin_is_within_lifetime' on 'c' after its storage has been released is not a constant expression" } + } + if (x == 2) + __builtin_is_within_lifetime (a); // { dg-error "'__builtin_is_within_lifetime' on 'c' after its storage has been released is not a constant expression" } + { + int c[42] = {}; + a = &c[40]; + if (!std::is_within_lifetime (a)) + return false; + } + if (x == 3) + __builtin_is_within_lifetime (a); // { dg-error "'__builtin_is_within_lifetime' on 'c' after its storage has been released is not a constant expression" } + { + decltype (nullptr) d = nullptr; + b = &d; + if (!std::is_within_lifetime (b)) + return false; + } + { + decltype (nullptr) d = nullptr; + if (x == 4) + __builtin_is_within_lifetime (b); // { dg-error "'__builtin_is_within_lifetime' on 'd' after its storage has been released is not a constant expression" } + } + if (x == 5) + __builtin_is_within_lifetime (b); // { dg-error "'__builtin_is_within_lifetime' on 'd' after its storage has been released is not a constant expression" } + { + decltype (nullptr) d[42] = {}; + b = &d[40]; + if (!std::is_within_lifetime (b)) + return false; + } + if (x == 6) + __builtin_is_within_lifetime (b); // { dg-error "'__builtin_is_within_lifetime' on 'd' after its storage has been released is not a constant expression" } + { + int c; + a = &c; + if (!std::is_within_lifetime (a)) + return false; + } + { + int c; + if (x == 7) + __builtin_is_within_lifetime (a); // { dg-error "'__builtin_is_within_lifetime' on 'c' after its storage has been released is not a constant expression" } + } + if (x == 8) + __builtin_is_within_lifetime (a); // { dg-error "'__builtin_is_within_lifetime' on 'c' after its storage has been released is not a constant expression" } + { + int c[42]; + a = &c[40]; + if (!std::is_within_lifetime (a)) + return false; + } + if (x == 9) + __builtin_is_within_lifetime (a); // { dg-error "'__builtin_is_within_lifetime' on 'c' after its storage has been released is not a constant expression" } + { + decltype (nullptr) d; + b = &d; + if (!std::is_within_lifetime (b)) + return false; + } + { + decltype (nullptr) d; + if (x == 10) + __builtin_is_within_lifetime (b); // { dg-error "'__builtin_is_within_lifetime' on 'd' after its storage has been released is not a constant expression" } + } + if (x == 11) + __builtin_is_within_lifetime (b); // { dg-error "'__builtin_is_within_lifetime' on 'd' after its storage has been released is not a constant expression" } + { + decltype (nullptr) d[42]; + b = &d[40]; + if (!std::is_within_lifetime (b)) + return false; + } + if (x == 12) + __builtin_is_within_lifetime (b); // { dg-error "'__builtin_is_within_lifetime' on 'd' after its storage has been released is not a constant expression" } + int e[2]; + if (!std::is_within_lifetime (&e) + || !std::is_within_lifetime (&e[0]) + || !std::is_within_lifetime (&e[1])) + return true; + std::destroy_at (&e[0]); + if (!std::is_within_lifetime (&e) + || std::is_within_lifetime (&e[0]) + || !std::is_within_lifetime (&e[1])) + return false; + std::construct_at (&e[0]); + std::destroy_at (&e[1]); + if (!std::is_within_lifetime (&e) + || !std::is_within_lifetime (&e[0]) + || std::is_within_lifetime (&e[1])) + return false; + std::construct_at (&e[1]); + if (!std::is_within_lifetime (&e) + || !std::is_within_lifetime (&e[0]) + || !std::is_within_lifetime (&e[1])) + return false; + struct { int a, b; } f; + if (!std::is_within_lifetime (&f) + || !std::is_within_lifetime (&f.a) + || !std::is_within_lifetime (&f.b)) + return true; + std::destroy_at (&f.a); + if (!std::is_within_lifetime (&f) + || std::is_within_lifetime (&f.a) + || !std::is_within_lifetime (&f.b)) + return false; + std::construct_at (&f.a); + std::destroy_at (&f.b); + if (!std::is_within_lifetime (&f) + || !std::is_within_lifetime (&f.a) + || std::is_within_lifetime (&f.b)) + return false; + std::construct_at (&f.b); + if (!std::is_within_lifetime (&f) + || !std::is_within_lifetime (&f.a) + || !std::is_within_lifetime (&f.b)) + return false; + return true; +} + +static_assert (bar (0)); +bool b = bar (1); // { dg-error "call to consteval function 'bar\\\(1\\\)' is not a constant expression" } +bool c = bar (2); // { dg-error "call to consteval function 'bar\\\(2\\\)' is not a constant expression" } +bool d = bar (3); // { dg-error "call to consteval function 'bar\\\(3\\\)' is not a constant expression" } +bool e = bar (4); // { dg-error "call to consteval function 'bar\\\(4\\\)' is not a constant expression" } +bool f = bar (5); // { dg-error "call to consteval function 'bar\\\(5\\\)' is not a constant expression" } +bool g = bar (6); // { dg-error "call to consteval function 'bar\\\(6\\\)' is not a constant expression" } +bool h = bar (7); // { dg-error "call to consteval function 'bar\\\(7\\\)' is not a constant expression" } +bool i = bar (8); // { dg-error "call to consteval function 'bar\\\(8\\\)' is not a constant expression" } +bool j = bar (9); // { dg-error "call to consteval function 'bar\\\(9\\\)' is not a constant expression" } +bool k = bar (10); // { dg-error "call to consteval function 'bar\\\(10\\\)' is not a constant expression" } +bool l = bar (11); // { dg-error "call to consteval function 'bar\\\(11\\\)' is not a constant expression" } +bool m = bar (12); // { dg-error "call to consteval function 'bar\\\(12\\\)' is not a constant expression" } + +struct A { int a = 42; bool b = std::is_within_lifetime (&a); }; +constexpr A n; + +struct B { consteval B () : a (42), b (std::is_within_lifetime (&a)) {} int a; bool b; }; +constexpr B o; + +struct C { consteval C () { __builtin_is_within_lifetime (this); } } p; + +struct D { + consteval D () : a (0), b (0), c (0) {} + consteval D (int x, int y, int z) + : a (x), b (y + std::is_within_lifetime (&this->a) * 32 + + std::is_within_lifetime (&this->b) * 64 + + std::is_within_lifetime (&this->c) * 128), c (z) {} + constexpr ~D () {} + int a, b, c; +}; + +consteval int +qux () +{ + D a[2] = {}; + std::destroy_at (&a[0]); + std::construct_at (&a[0], 4, 5, 6); + return a[0].a + a[0].b + a[0].c; +} + +// FIXME: In mem-initializer of b, a should be already constructed, +// so within lifetime, but b is in the middle of construction and c +// construction has not started yet. +static_assert (qux () == 4 + 5 + 6 + 32); // { dg-bogus "note: the comparison reduces to '\\\(239 == 47\\\)'" "" { xfail *-*-* } } + // { dg-bogus "static assertion failed" "" { xfail *-*-* } .-1 } + +consteval bool +corge (bool x) +{ + int *p; + { + int a; + p = &a; + if (!std::is_within_lifetime (&a)) + return false; + std::destroy_at (&a); + if (std::is_within_lifetime (&a)) + return false; + std::construct_at (&a); + if (!std::is_within_lifetime (&a)) + return false; + } + if (x) + __builtin_is_within_lifetime (p); // { dg-error "'__builtin_is_within_lifetime' on 'a' after its storage has been released is not a constant expression" } + return true; +} + +static_assert (corge (false)); +bool q = corge (true); // { dg-error "call to consteval function 'corge\\\(true\\\)' is not a constant expression" } diff --git a/gcc/testsuite/g++.dg/cpp26/within-lifetime4.C b/gcc/testsuite/g++.dg/cpp26/within-lifetime4.C new file mode 100644 index 000000000000..b420dea5fe2d --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime4.C @@ -0,0 +1,157 @@ +// P3450R1 - Extend std::is_within_lifetime +// { dg-do compile { target c++20 } } + +template <typename T> +consteval bool baz (const T *p) { return __builtin_is_within_lifetime (p); } +// { dg-error "'__builtin_is_within_lifetime' on 'a' from outside current evaluation is not a constant expression" "" { target *-*-* } .-1 } +bool a = baz (&a); // { dg-error "is not a constant expression" } +constexpr bool b = false; + +void +foo (int *p) +{ + __builtin_is_within_lifetime (&b); + __builtin_is_within_lifetime (nullptr); // { dg-error "'__builtin_is_within_lifetime' argument type 'std::nullptr_t' is not pointer type" } + __builtin_is_within_lifetime (static_cast <int *> (nullptr)); // { dg-error "is not a constant expression" } + // { dg-error "'__builtin_is_within_lifetime' called with a null pointer" "" { target *-*-* } .-1 } + __builtin_is_within_lifetime (&foo); // { dg-error "is not a constant expression" } + // { dg-error "'__builtin_is_within_lifetime' called with pointer to function" "" { target *-*-* } .-1 } + __builtin_is_within_lifetime (0); // { dg-error "'__builtin_is_within_lifetime' argument type 'int' is not pointer type" } + __builtin_is_within_lifetime (0.0); // { dg-error "'__builtin_is_within_lifetime' argument type 'double' is not pointer type" } + __builtin_is_within_lifetime (); // { dg-error "'__builtin_is_within_lifetime' needs a single argument" } + __builtin_is_within_lifetime (&b, &b); // { dg-error "'__builtin_is_within_lifetime' needs a single argument" } + __builtin_is_within_lifetime (p); // { dg-error "call to consteval function '__builtin_is_within_lifetime\\\(p\\\)' is not a constant expression" } + // { dg-error "'p' is not a constant expression" "" { target *-*-* } .-1 } +} + +extern int v; + +consteval bool +bar (int x) +{ + switch (x) + { + case 0: __builtin_is_within_lifetime (&b); break; + case 1: __builtin_is_within_lifetime (static_cast <int *> (nullptr)); break; // { dg-error "'__builtin_is_within_lifetime' called with a null pointer" } + case 2: __builtin_is_within_lifetime (&foo); break; // { dg-error "'__builtin_is_within_lifetime' called with pointer to function" } + case 3: __builtin_is_within_lifetime (&v); break; // { dg-error "'__builtin_is_within_lifetime' on 'v' from outside current evaluation is not a constant expression" } + } + return true; +} + +static_assert (bar (0)); +bool c = bar (1); // { dg-error "is not a constant expression" } +bool d = bar (2); // { dg-error "is not a constant expression" } +bool e = bar (3); // { dg-error "is not a constant expression" } + +constexpr struct { union { int a; short b; }; mutable int c; } g = { .b = 42 }; +constexpr int s = 42; +constexpr int t[2] = {}; + +consteval bool +qux (int x) +{ + if (!__builtin_is_within_lifetime (&g)) + return false; + if (__builtin_is_within_lifetime (&g.a)) + return false; + if (!__builtin_is_within_lifetime (&g.b)) + return false; + if (x == 1) + __builtin_is_within_lifetime (&g.c); // { dg-error "'__builtin_is_within_lifetime' on 'mutable' sub-object '<unnamed struct>::c'" } + else if (x == 2) + __builtin_is_within_lifetime (&s + 1); // { dg-error "'__builtin_is_within_lifetime' on '\\\*\\\(\\\(\\\& s\\\) \\\+ 4\\\)' from outside current evaluation is not a constant expression" } + else if (x == 3) + __builtin_is_within_lifetime (t + 2); // { dg-error "array subscript value '2' is outside the bounds of array 't' of type 'const int \\\[2\\\]'" } + return true; +} + +static_assert (qux (0)); +bool h = qux (1); // { dg-error "is not a constant expression" } +bool i = qux (2); // { dg-error "is not a constant expression" } +bool j = qux (3); // { dg-error "is not a constant expression" } + +struct A { + constexpr A () {} + constexpr A (const A &) {} + constexpr ~A () {} +}; + +template <typename T> +constexpr T & +corge (T &&x) +{ + return static_cast <T &> (x); +} + +consteval bool +fred () +{ + static_assert (__builtin_is_within_lifetime (&corge (0))); + static_assert (__builtin_is_within_lifetime (&corge (A {}))); + if (!__builtin_is_within_lifetime (&corge (0))) + return false; + if (!__builtin_is_within_lifetime (&corge (A {}))) + return false; + return true; +} + +static_assert (fred ()); + +constexpr const int &k = 0; +static_assert (__builtin_is_within_lifetime (&k)); + +template <typename T> +consteval T * +waldo () +{ + T t; + return &t; // { dg-warning "address of local variable 't' returned" } + +} + +constexpr bool l = __builtin_is_within_lifetime (waldo <int> ()); // { dg-error "'__builtin_is_within_lifetime' on 't' after its storage has been released is not a constant expression" } +constexpr bool m = __builtin_is_within_lifetime (waldo <int [2]> ()); // { dg-error "'__builtin_is_within_lifetime' on 't' after its storage has been released is not a constant expression" } + +template <typename T, T V> +struct integral_constant +{ + static constexpr T value = V; + using value_type = T; + using type = integral_constant <T, V>; + constexpr operator value_type () const noexcept { return value; } + constexpr value_type operator () () const noexcept { return value; } +}; + +template <bool V> +using bool_constant = integral_constant <bool, V>; + +using true_type = bool_constant <true>; +using false_type = bool_constant <false>; + +constexpr int n = 42; + +template <auto T> +concept B = bool_constant <__builtin_is_within_lifetime (T ())>::value; + +static_assert (B <[] { return &n; }>); +static_assert (!B <[] { return static_cast <int *> (nullptr); }>); +static_assert (!B <[] { return static_cast <void (*) (int *)> (&foo); }>); + +template <auto T> +constexpr true_type +garply () requires B <T> +{ + return {}; +} + +template <auto T> +false_type +garply () +{ + return {}; +} + +static_assert (decltype (garply <[] { return &n; }> ())::value); +static_assert (!decltype (garply <[] { return static_cast <int *> (nullptr); }> ())::value); +true_type (*o) () = &garply <[] { return &n; }>; diff --git a/gcc/testsuite/g++.dg/cpp26/within-lifetime5.C b/gcc/testsuite/g++.dg/cpp26/within-lifetime5.C new file mode 100644 index 000000000000..1f780cf13d58 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime5.C @@ -0,0 +1,46 @@ +// P3450R1 - Extend std::is_within_lifetime +// { dg-do compile { target c++23 } } +// { dg-skip-if "sizeof (bool) > 1" { powerpc-*-darwin* } } + +namespace std { + template <class T> + consteval bool + is_within_lifetime (const T *p) noexcept + { + return __builtin_is_within_lifetime (p); + } +} + +struct OptBool { + union { bool b; char c; }; + + // note: this assumes common implementation properties for bool and char: + // * sizeof (bool) == sizeof (char), and + // * the value representations for true and false are distinct + // from the value representation for 2 + constexpr OptBool () : c (2) { } + constexpr OptBool (bool b) : b (b) { } + + constexpr bool has_value () const + { + if consteval + { + return std::is_within_lifetime (&b); // during constant evaluation, cannot read from c + } + else + { + return c != 2; // during runtime, must read from c + } + } + + constexpr const bool &operator * () const { return b; } +}; + +constexpr OptBool disengaged; +constexpr OptBool engaged (true); +static_assert (!disengaged.has_value ()); +static_assert (engaged.has_value ()); +static_assert (*engaged); +constexpr OptBool engaged2 (false); +static_assert (engaged2.has_value ()); +static_assert (!*engaged2); diff --git a/gcc/testsuite/g++.dg/cpp26/within-lifetime6.C b/gcc/testsuite/g++.dg/cpp26/within-lifetime6.C new file mode 100644 index 000000000000..6e9ddf917bdc --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime6.C @@ -0,0 +1,41 @@ +// P3450R1 - Extend std::is_within_lifetime +// { dg-do compile { target c++26 } } +// { dg-skip-if "sizeof (bool) > 1" { powerpc-*-darwin* } } + +#include <type_traits> + +#if __cpp_lib_within_lifetime >= 202306L +struct OptBool { + union { bool b; char c; }; + + // note: this assumes common implementation properties for bool and char: + // * sizeof (bool) == sizeof (char), and + // * the value representations for true and false are distinct + // from the value representation for 2 + constexpr OptBool () : c (2) { } + constexpr OptBool (bool b) : b (b) { } + + constexpr bool has_value () const + { + if consteval + { + return std::is_within_lifetime (&b); // during constant evaluation, cannot read from c + } + else + { + return c != 2; // during runtime, must read from c + } + } + + constexpr const bool &operator * () const { return b; } +}; +#endif + +constexpr OptBool disengaged; +constexpr OptBool engaged (true); +static_assert (!disengaged.has_value ()); +static_assert (engaged.has_value ()); +static_assert (*engaged); +constexpr OptBool engaged2 (false); +static_assert (engaged2.has_value ()); +static_assert (!*engaged2); diff --git a/gcc/testsuite/g++.dg/cpp26/within-lifetime7.C b/gcc/testsuite/g++.dg/cpp26/within-lifetime7.C new file mode 100644 index 000000000000..d920f5aee0cd --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/within-lifetime7.C @@ -0,0 +1,75 @@ +// P3450R1 - Extend std::is_within_lifetime +// { dg-do compile { target c++20 } } +// { dg-options "" } + +#include "../cpp2a/construct_at.h" + +consteval bool +foo (int n) +{ + int a[n]; + if (!__builtin_is_within_lifetime (&a)) + return false; + if (!__builtin_is_within_lifetime (&a[0])) + return false; + if (!__builtin_is_within_lifetime (&a[n / 2])) + return false; + if (!__builtin_is_within_lifetime (&a[n - 1])) + return false; + std::destroy_at (&a[0]); + if (!__builtin_is_within_lifetime (&a)) + return false; + if (__builtin_is_within_lifetime (&a[0])) + return false; + if (!__builtin_is_within_lifetime (&a[n / 2])) + return false; + if (!__builtin_is_within_lifetime (&a[n - 1])) + return false; + std::construct_at (&a[0]); + std::destroy_at (&a[n / 2]); + if (!__builtin_is_within_lifetime (&a)) + return false; + if (!__builtin_is_within_lifetime (&a[0])) + return false; + if (__builtin_is_within_lifetime (&a[n / 2])) + return false; + if (!__builtin_is_within_lifetime (&a[n - 1])) + return false; + std::construct_at (&a[n / 2]); + std::destroy_at (&a[n - 1]); + if (!__builtin_is_within_lifetime (&a)) + return false; + if (!__builtin_is_within_lifetime (&a[0])) + return false; + if (!__builtin_is_within_lifetime (&a[n / 2])) + return false; + if (__builtin_is_within_lifetime (&a[n - 1])) + return false; + std::construct_at (&a[n - 1]); + _Complex double b = 1.0; + if (!__builtin_is_within_lifetime (&b)) + return false; + if (!__builtin_is_within_lifetime (&__real__ b)) + return false; + if (!__builtin_is_within_lifetime (&__imag__ b)) + return false; + std::destroy_at (&__real__ b); + if (!__builtin_is_within_lifetime (&b)) + return false; + if (__builtin_is_within_lifetime (&__real__ b)) + return false; + if (!__builtin_is_within_lifetime (&__imag__ b)) + return false; + std::construct_at (&__real__ b); + std::destroy_at (&__imag__ b); + if (!__builtin_is_within_lifetime (&b)) + return false; + if (!__builtin_is_within_lifetime (&__real__ b)) + return false; + if (__builtin_is_within_lifetime (&__imag__ b)) + return false; + std::construct_at (&__imag__ b); + return true; +} + +static_assert (foo (42)); diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 81bbf0ea63ab..30cc3de51b31 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -2496,6 +2496,15 @@ ftms = { }; }; +ftms = { + name = within_lifetime; + values = { + v = 202306; + cxxmin = 26; + extra_cond = "__has_builtin(__builtin_is_within_lifetime)"; + }; +}; + ftms = { name = hardened_array; values = { diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index 786db34d6be7..08e0f1a4b3bc 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -2749,6 +2749,16 @@ #endif /* !defined(__cpp_lib_valarray) */ #undef __glibcxx_want_valarray +#if !defined(__cpp_lib_within_lifetime) +# if (__cplusplus > 202302L) && (__has_builtin(__builtin_is_within_lifetime)) +# define __glibcxx_within_lifetime 202306L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_within_lifetime) +# define __cpp_lib_within_lifetime 202306L +# endif +# endif +#endif /* !defined(__cpp_lib_within_lifetime) */ +#undef __glibcxx_want_within_lifetime + #if !defined(__cpp_lib_hardened_array) # if (__cplusplus >= 201103L) && (defined(_GLIBCXX_ASSERTIONS)) # define __glibcxx_hardened_array 202502L diff --git a/libstdc++-v3/include/std/type_traits b/libstdc++-v3/include/std/type_traits index 2b3f10016a29..921ef287f839 100644 --- a/libstdc++-v3/include/std/type_traits +++ b/libstdc++-v3/include/std/type_traits @@ -66,6 +66,7 @@ #define __glibcxx_want_type_trait_variable_templates #define __glibcxx_want_unwrap_ref #define __glibcxx_want_void_t +#define __glibcxx_want_within_lifetime #include <bits/version.h> extern "C++" @@ -4400,6 +4401,13 @@ template<typename _Ret, typename _Fn, typename... _Args> }; #endif // C++11 +#if __cpp_lib_within_lifetime >= 202306L // C++ >= 26 + template<typename _Tp> + consteval bool + is_within_lifetime(const _Tp* __p) noexcept + { return __builtin_is_within_lifetime (__p); } +#endif + /// @} group metaprogramming _GLIBCXX_END_NAMESPACE_VERSION diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/std.cc.in index ddc1f04ba354..2449af2e8f05 100644 --- a/libstdc++-v3/src/c++23/std.cc.in +++ b/libstdc++-v3/src/c++23/std.cc.in @@ -3610,6 +3610,9 @@ export namespace std using std::is_structural; using std::is_structural_v; #endif +#if __cpp_lib_within_lifetime >= 202306L + using std::is_within_lifetime; +#endif } // <typeindex>
