Bootstrapped/regtested on x86_64-pc-linux-gnu and ppc64le-pc-linux-gnu, ok for trunk?
-- >8 -- This patch implements the second half of P2686R5, references to constexpr variables. The first half (constexpr structured bindings) was implemented by Jakub. I used Jakub's WIP patch and (mostly) incorporated Jason's comments in: <https://gcc.gnu.org/pipermail/gcc-patches/2025-September/694469.html> This proposal makes void f () { int i = 0; constexpr int &r = i; constexpr int *p = &i; } valid by using the symbolic addressing approach: constexpr references designate a specific object, rather than an address, and retain that information across constant evaluation contexts. Note that r and p will be rejected if made static: the stack frames of r/i and p/i must match. What this paper means is that in C++26 static and constexpr initialization is not the same thing as it used to be. The former is about "can this value be emitted as static data" and the latter is about "is this value a valid result of a constant evaluation in the C++26 sense". Currently, we have reduced_constant_expression_p for both. This patch removes that and introduces two new predicates: valid_static_initializer_p (~ static init) and constexpr_representable_p (~new C++26 rules). Sadly, the CONSTRUCTOR_NO_CLEARING flag is still mutated in them; I couldn't find a reasonable way to fix that. constexpr-referenceability is not a static property; an object is (or isn't) constexpr-referenceable from a point P. Thus, constexpr_point_for_decl. A word on temporaries. We have temporaries whose lifetime has been extended, and temporaries whose lifetime has not been extended, but we have no way to tell them apart (except by looking into the constexpr hash map as in this patch). See CWG 1634 which proposes that a non-extended temporary has its own distinct storage duration. Temporaries whose lifetime has not been extended are never constexpr- referenceable. So: struct A { int a; const int &b; }; void f() { constexpr A e1 = { 1, 2 }; // OK, temp extended to e1 constexpr A e2(1, 2); // error: e2.b dangles } This patch doesn't add a flag to distinguish them. Instead, to fix a crash in constexpr-ref5.C, I added the find_dangling_ref_r walk and now we say for e2: error: constant evaluation refers to a temporary whose lifetime has ended The decl_maybe_constant_var_p call in set_up_extended_ref_temp is there solely because of tree-ssa/initlist-opt5.C. This patch doesn't change mark_use because I didn't need to adjust it for the ODR wording. Two bugs that came up: * we get a wrong "the value of '<temporary>' is not usable in a constant expression" (PR101557), * we still don't reject the following (PR70151): static constexpr int a = 4; static constexpr const int *b = &a + 2; PR c++/117784 gcc/cp/ChangeLog: * call.cc (set_up_extended_ref_temp): Constant-initialize the temporary also if it is constexpr-representable in C++26. * constexpr.cc (verify_constant): Adjust declaration. New tree parameter. Call constexpr_representable_p instead of reduced_constant_expression_p. (exception_what_str): Call valid_static_initializer_p instead of reduced_constant_expression_p. (diagnose_std_terminate): Likewise. (diagnose_uncaught_exception): Likewise. (cxx_eval_call_expression): Likewise. (addr_expr_base): New. (constexpr_point_for_decl): New. (constexpr_referenceable_p): New. (reduced_constant_expression_p): Rename to... (constexpr_representable_p): ...this. Make static. New parameters. Implement P2686. (constexpr_representable_p): New overload. (valid_static_initializer_p): New. (cxx_eval_constant_expression): Call valid_static_initializer_p instead of reduced_constant_expression_p. (struct dangling_data): New. (find_dangling_ref_r): New. (cxx_eval_outermost_constant_expr): Detect dangling references. * cp-tree.h (reduced_constant_expression_p): Remove. (constexpr_point_for_decl): Declare. (constexpr_representable_p): Declare. (valid_static_initializer_p): Declare. * init.cc (constant_value_1): Call constexpr_representable_p in C++26. (build_vec_init): Call valid_static_initializer_p instead of reduced_constant_expression_p. * pt.cc (invalid_tparm_referent_p): Add auto_diagnostic_group and an inform call. (get_template_parm_object): Call valid_static_initializer_p instead of reduced_constant_expression_p. (convert_nontype_argument): Likewise. (tsubst_expr): Likewise. * semantics.cc (cexpr_str::extract): Likewise. * tree.cc (build_target_expr): Likewise. * typeck2.cc (store_init_value): Call constexpr_representable_p instead of reduced_constant_expression_p. Call split_nonconstant_init when !valid_static_initializer_p for statics or when !constexpr_representable_p otherwise, not reduced_constant_expression_p. libstdc++-v3/ChangeLog: * testsuite/20_util/function_ref/dangling_neg.cc: Adjust dg-error. gcc/testsuite/ChangeLog: * g++.dg/cpp0x/constexpr-initlist11a.C: Adjust dg-error. Add a static_assert. * g++.dg/cpp0x/constexpr-ref12.C: Only expect an error message in C++23 and down. * g++.dg/cpp0x/constexpr-ref2.C: Likewise. * g++.dg/cpp0x/lambda/lambda-constexpr3a.C: Likewise. * g++.dg/cpp1y/constexpr-nsdmi6a.C: Likewise. * g++.dg/cpp1y/constexpr-nsdmi6b.C: Likewise. * g++.dg/cpp26/expansion-stmt22.C: Likewise. * g++.dg/cpp2a/nontype-class64.C: Adjust dg-error. * g++.dg/cpp2a/nontype-class68.C: Likewise. * g++.dg/diagnostic/constexpr3.C: Only expect error messages in C++23 and down. * g++.dg/reflect/value1.C: Remove a dg-error. * g++.dg/cpp26/constexpr-ref1.C: New test. * g++.dg/cpp26/constexpr-ref10.C: New test. * g++.dg/cpp26/constexpr-ref2.C: New test. * g++.dg/cpp26/constexpr-ref3.C: New test. * g++.dg/cpp26/constexpr-ref4.C: New test. * g++.dg/cpp26/constexpr-ref5.C: New test. * g++.dg/cpp26/constexpr-ref6.C: New test. * g++.dg/cpp26/constexpr-ref7.C: New test. * g++.dg/cpp26/constexpr-ref8.C: New test. * g++.dg/cpp26/constexpr-ref9.C: New test. * g++.dg/cpp26/expansion-stmt46.C: New test. Co-authored-by: Jakub Jelinek <[email protected]> --- gcc/cp/call.cc | 15 +- gcc/cp/constexpr.cc | 270 +++++++++++++++--- gcc/cp/cp-tree.h | 4 +- gcc/cp/init.cc | 8 +- gcc/cp/pt.cc | 14 +- gcc/cp/semantics.cc | 2 +- gcc/cp/tree.cc | 2 +- gcc/cp/typeck2.cc | 12 +- .../g++.dg/cpp0x/constexpr-initlist11a.C | 5 +- gcc/testsuite/g++.dg/cpp0x/constexpr-ref12.C | 6 +- gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C | 4 +- .../g++.dg/cpp0x/lambda/lambda-constexpr3a.C | 6 +- .../g++.dg/cpp1y/constexpr-nsdmi6a.C | 4 +- .../g++.dg/cpp1y/constexpr-nsdmi6b.C | 4 +- gcc/testsuite/g++.dg/cpp26/constexpr-ref1.C | 81 ++++++ gcc/testsuite/g++.dg/cpp26/constexpr-ref10.C | 22 ++ gcc/testsuite/g++.dg/cpp26/constexpr-ref2.C | 28 ++ gcc/testsuite/g++.dg/cpp26/constexpr-ref3.C | 15 + gcc/testsuite/g++.dg/cpp26/constexpr-ref4.C | 27 ++ gcc/testsuite/g++.dg/cpp26/constexpr-ref5.C | 34 +++ gcc/testsuite/g++.dg/cpp26/constexpr-ref6.C | 96 +++++++ gcc/testsuite/g++.dg/cpp26/constexpr-ref7.C | 27 ++ gcc/testsuite/g++.dg/cpp26/constexpr-ref8.C | 30 ++ gcc/testsuite/g++.dg/cpp26/constexpr-ref9.C | 28 ++ gcc/testsuite/g++.dg/cpp26/expansion-stmt22.C | 2 +- gcc/testsuite/g++.dg/cpp26/expansion-stmt46.C | 14 + gcc/testsuite/g++.dg/cpp2a/nontype-class64.C | 2 +- gcc/testsuite/g++.dg/cpp2a/nontype-class68.C | 4 +- gcc/testsuite/g++.dg/diagnostic/constexpr3.C | 18 +- gcc/testsuite/g++.dg/reflect/value1.C | 10 +- .../20_util/function_ref/dangling_neg.cc | 4 +- 31 files changed, 713 insertions(+), 85 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp26/constexpr-ref1.C create mode 100644 gcc/testsuite/g++.dg/cpp26/constexpr-ref10.C create mode 100644 gcc/testsuite/g++.dg/cpp26/constexpr-ref2.C create mode 100644 gcc/testsuite/g++.dg/cpp26/constexpr-ref3.C create mode 100644 gcc/testsuite/g++.dg/cpp26/constexpr-ref4.C create mode 100644 gcc/testsuite/g++.dg/cpp26/constexpr-ref5.C create mode 100644 gcc/testsuite/g++.dg/cpp26/constexpr-ref6.C create mode 100644 gcc/testsuite/g++.dg/cpp26/constexpr-ref7.C create mode 100644 gcc/testsuite/g++.dg/cpp26/constexpr-ref8.C create mode 100644 gcc/testsuite/g++.dg/cpp26/constexpr-ref9.C create mode 100644 gcc/testsuite/g++.dg/cpp26/expansion-stmt46.C diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index 13b2e4ccbef..a99cd26d1f7 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -14423,7 +14423,20 @@ set_up_extended_ref_temp (tree decl, tree expr, vec<tree, va_gc> **cleanups, init = maybe_constant_init (expr, var, /*manifestly_const_eval=*/true); /* As in store_init_value. */ init = cp_fully_fold (init); - if (TREE_CONSTANT (init)) + if (TREE_CONSTANT (init) + /* In C++26, allow + struct A { int *const &r; }; + void f (int x) { constexpr A a = { &x }; } + where &x isn't TREE_CONSTANT, but the temporary should be + constant-initialized if the variable the temporary is extended to + may be usable in constant expressions. The decl_maybe_constant_var_p + check is motivated by c++/105838 where we want to build + initializer_list<string> in a loop and not as a series of inline + stores. */ + || (cxx_dialect >= cxx26 + && !TREE_STATIC (var) + && decl_maybe_constant_var_p (decl) + && constexpr_representable_p (init, constexpr_point_for_decl (var)))) { if (literal_type_p (type) && CP_TYPE_CONST_NON_VOLATILE_P (type) diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc index 31ef5bf1111..32e26268624 100644 --- a/gcc/cp/constexpr.cc +++ b/gcc/cp/constexpr.cc @@ -43,7 +43,8 @@ along with GCC; see the file COPYING3. If not see #include "toplev.h" #include "contracts.h" -static bool verify_constant (tree, bool, bool *, bool *); +static bool verify_constant (tree, bool, bool *, bool *, + tree = constexpr_point_for_decl (NULL_TREE)); #define VERIFY_CONSTANT(X) \ do { \ if (verify_constant ((X), ctx->quiet, non_constant_p, overflow_p)) \ @@ -1725,7 +1726,7 @@ exception_what_str (const constexpr_ctx *ctx, tree exc) &jmp_target); if (throws (&jmp_target) || non_constant_p) return NULL; - if (reduced_constant_expression_p (ptr)) + if (valid_static_initializer_p (ptr)) if (const char *msg = c_getstr (ptr)) return xstrdup (msg); auto_vec <char, 32> v; @@ -1776,7 +1777,7 @@ diagnose_std_terminate (location_t loc, const constexpr_ctx *ctx, tree exc) &non_constant_p, &overflow_p, &jmp_target); gcc_assert (!throws (&jmp_target) && !non_constant_p); - if (reduced_constant_expression_p (val)) + if (valid_static_initializer_p (val)) error_at (loc, "%qs called after throwing an exception %qE", "std::terminate", val); else @@ -1807,7 +1808,7 @@ diagnose_uncaught_exception (location_t loc, const constexpr_ctx *ctx, tree exc) &non_constant_p, &overflow_p, &jmp_target); gcc_assert (!throws (&jmp_target) && !non_constant_p); - if (reduced_constant_expression_p (val)) + if (valid_static_initializer_p (val)) error_at (loc, "uncaught exception %qE", val); else error_at (loc, "uncaught exception of type %qT", type); @@ -5172,7 +5173,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, } /* Only cache a permitted result of a constant expression. */ - if (cacheable && !reduced_constant_expression_p (result)) + if (cacheable && !valid_static_initializer_p (result)) cacheable = false; /* Only cache a result without contract violations. */ @@ -5213,7 +5214,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, However, in C++20, a constexpr constructor doesn't necessarily have to initialize all the fields, so we don't clear CONSTRUCTOR_NO_CLEARING in order to detect reading an uninitialized object in constexpr instead - of value-initializing it. (reduced_constant_expression_p is expected to + of value-initializing it. (constexpr_representable_p is expected to take care of clearing the flag.) */ if (TREE_CODE (result) == CONSTRUCTOR && (cxx_dialect < cxx20 @@ -5240,24 +5241,113 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, return result; } -/* Return true if T is a valid constant initializer. If a CONSTRUCTOR - 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. +/* For T, an ADDR_EXPR, peel the ADDR_EXPR and component references and return + the innermost tree. */ + +static tree +addr_expr_base (tree t) +{ + gcc_checking_assert (TREE_CODE (t) == ADDR_EXPR); + t = TREE_OPERAND (t, 0); + while (TREE_CODE (t) == COMPONENT_REF + || (TREE_CODE (t) == ARRAY_REF + && TREE_CODE (TREE_OPERAND (t, 1)) == INTEGER_CST) + || TREE_CODE (t) == REALPART_EXPR + || TREE_CODE (t) == IMAGPART_EXPR) + t = TREE_OPERAND (t, 0); + return t; +} + +/* Figure out the point P for constexpr_representable_p given a DECL. Here, + NULL_TREE means a point at namespace scope, from which only objects + with static storage duration are referenceable. */ + +tree +constexpr_point_for_decl (tree decl) +{ + if (decl && VAR_P (decl)) + return (decl_storage_duration (decl) == dk_auto + ? decl_function_context (decl) + : NULL_TREE); + + if (!call_stack.is_empty ()) + { + tree call = call_stack.last (); + return cp_get_callee_fndecl_nofold (call); + } + + return NULL_TREE; +} + +/* Return true if an object O is constexpr-referenceable from a point POINT + as per [expr.const.init]/2: + An object o is constexpr-referenceable from a point P if + -- o has static storage duration, or + -- o has automatic storage duration, and, letting v denote + -- the variable corresponding to o's complete object or + -- the variable to whose lifetime that of o is extended, + the smallest scope enclosing v and the smallest scope enclosing P + that are neither + -- block scopes nor + -- function parameter scopes associated with a requirement-parameter-list + are the same function parameter scope. */ + +static bool +constexpr_referenceable_p (tree o, tree point) +{ + /* String-literals have static storage duration ([lex.string]). */ + if (TREE_CODE (o) == STRING_CST) + return true; + + if (!(TREE_CODE (o) == PARM_DECL + || TREE_CODE (o) == FUNCTION_DECL + || VAR_P (o))) + return false; + + switch (decl_storage_duration (o)) + { + case dk_static: + return true; + case dk_thread: + case dk_dynamic: + return false; + case dk_auto: + return decl_function_context (o) == point; + } + + gcc_unreachable (); +} + +/* Return true if T is constexpr-representable at a point POINT: + [expr.const.init]/3: An object or reference x is constexpr-representable + at a point P if, for each constituent value of x that points to or past + an object o, and for each constituent reference of x that refers to an + object o, o is constexpr-referenceable from P. + + If a CONSTRUCTOR 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_TREE. 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. */ + subobjects. STRICT_P is true if we are enforcing the language rules + about what is a constant expression; false if we are deciding whether + something can serve as a static initializer (before C++26 the rules are + equivalent, so STRICT_P is moot). */ -bool -reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */, - bool union_elemental_subobject /* = false */) +static bool +constexpr_representable_p (tree t, tree point, tree sz, + bool union_elemental_subobject, bool strict_p) { if (t == NULL_TREE) return false; + if (CONSTANT_CLASS_P (t)) + return true; + + /* We should enforce the P2686 rules only in C++26. */ + strict_p &= cxx_dialect >= cxx26; + switch (TREE_CODE (t)) { - case PTRMEM_CST: case REFLECT_EXPR: /* Even if we can't lower this yet, it's constant. */ return true; @@ -5288,8 +5378,10 @@ reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */, tree cursor = min; for (auto &e: CONSTRUCTOR_ELTS (t)) { - if (!reduced_constant_expression_p - (e.value, NULL_TREE, union_elemental_subobject)) + if (!constexpr_representable_p (e.value, point, + /*sz=*/NULL_TREE, + union_elemental_subobject, + strict_p)) return false; if (array_index_cmp (cursor, e.index) != 0) return false; @@ -5334,13 +5426,14 @@ reduced_constant_expression_p (tree t, tree sz /* = NULL_TREE */, { /* If VAL is null, we're in the middle of initializing this element. */ - if (!reduced_constant_expression_p (e.value, - (e.index - && (TREE_CODE (e.index) - == FIELD_DECL)) - ? DECL_SIZE (e.index) - : NULL_TREE, - union_elemental_subobject)) + if (!constexpr_representable_p (e.value, point, + (e.index + && (TREE_CODE (e.index) + == FIELD_DECL)) + ? DECL_SIZE (e.index) + : NULL_TREE, + union_elemental_subobject, + strict_p)) return false; /* We want to remove initializers for empty fields in a struct to avoid confusing output_constructor. */ @@ -5376,13 +5469,68 @@ ok: CONSTRUCTOR_NO_CLEARING (t) = false; return true; + case ADDR_EXPR: + if (!strict_p) + break; + /* This is just an optimization: a TREE_CONSTANT address has a static + base, so it's referenceable from anywhere. */ + if (TREE_CONSTANT (t)) + return true; + return constexpr_referenceable_p (addr_expr_base (t), point); + + case POINTER_PLUS_EXPR: + if (!strict_p) + break; + if (TREE_CODE (TREE_OPERAND (t, 1)) != INTEGER_CST) + return false; + return constexpr_representable_p (TREE_OPERAND (t, 0), point, + /*sz=*/NULL_TREE, + union_elemental_subobject, + strict_p); + + CASE_CONVERT: + if (!strict_p) + break; + if (!POINTER_TYPE_P (TREE_TYPE (t)) + || !POINTER_TYPE_P (TREE_TYPE (TREE_OPERAND (t, 0)))) + break; + return constexpr_representable_p (TREE_OPERAND (t, 0), point, + /*sz=*/NULL_TREE, + union_elemental_subobject, + strict_p); + default: - /* FIXME are we calling this too much? */ - return initializer_constant_valid_p (t, TREE_TYPE (t)) != NULL_TREE; + break; } + + return initializer_constant_valid_p (t, TREE_TYPE (t)) != NULL_TREE; +} + +/* Return true if T is constexpr-representable at a point POINT. In C++26, + this means enforcing the P2686 rules. */ + +bool +constexpr_representable_p (tree t, tree point) +{ + return constexpr_representable_p (t, point, /*sz=*/NULL_TREE, + /*union_elemental_subobject=*/false, + /*strict_p=*/true); } -/* *TP was not deemed constant by reduced_constant_expression_p. Explain +/* Return true if T, a reduced value, can be used as a static initializer. + This has more to do with what our middle end can emit statically (which + is allowed by [basic.start.static]); for determining if something is + a constant expression, see constexpr_representable_p. */ + +bool +valid_static_initializer_p (tree t) +{ + return constexpr_representable_p (t, /*point=*/NULL_TREE, /*sz=*/NULL_TREE, + /*union_elemental_subobject=*/false, + /*strict_p=*/false); +} + +/* *TP was not deemed constant by constexpr_representable_p. Explain why and suggest what could be done about it. */ static tree @@ -5444,13 +5592,15 @@ verify_constant_explain_r (tree *tp, int *walk_subtrees, void *) We only call this in places that require an arithmetic constant, not in places where we might have a non-constant expression that can be a component of a constant expression, such as the address of a constexpr - variable that might be dereferenced later. */ + variable that might be dereferenced later. POINT is the point used when + checking if T is constexpr-representable. */ static bool verify_constant (tree t, bool allow_non_constant, bool *non_constant_p, - bool *overflow_p) + bool *overflow_p, + tree point/*=constexpr_point_for_decl (NULL_TREE)*/) { - if (!*non_constant_p && !reduced_constant_expression_p (t) + if (!*non_constant_p && !constexpr_representable_p (t, point) && t != void_node) { if (!allow_non_constant) @@ -10677,7 +10827,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t, break; case CONSTRUCTOR: - if (TREE_CONSTANT (t) && reduced_constant_expression_p (t)) + if (TREE_CONSTANT (t) && valid_static_initializer_p (t)) { /* Don't re-process a constant CONSTRUCTOR. */ verify_constructor_flags (t); @@ -11510,6 +11660,34 @@ check_for_failed_contracts (constexpr_ctx *ctx) return error; } +struct dangling_data { + constexpr_global_ctx *global; + tree object; +}; + +/* Helper to find &D.1234 where D.1234 is a temporary whose lifetime hasn't + been extended. */ + +static tree +find_dangling_ref_r (tree *tp, int *walk_subtrees, void *data_) +{ + auto data = static_cast <dangling_data *> (data_); + tree t = *tp; + + if (TYPE_P (t)) + *walk_subtrees = 0; + else if (TREE_CODE (t) == ADDR_EXPR) + { + t = addr_expr_base (t); + if (t != data->object + && data->global->get_value_ptr (t, /*initializing=*/false)) + return t; + *walk_subtrees = 0; + } + + return NULL_TREE; +} + /* ALLOW_NON_CONSTANT is false if T is required to be a constant expression. STRICT has the same sense as for constant_value_1: true if we only allow conforming C++ constant expressions, or false if we want a constant value @@ -11803,10 +11981,34 @@ cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant, non_constant_p = true; } + /* If OBJECT is the slot of a TARGET_EXPR, it can be elided or copied to + the object being initialized, so it isn't itself that object. */ + tree init_object = (TREE_CODE (t) == TARGET_EXPR + && object == TARGET_EXPR_SLOT (t) + ? NULL_TREE : object); if (!non_constant_p && !constexpr_dtor) - verify_constant (r, allow_non_constant, &non_constant_p, &overflow_p); + verify_constant (r, allow_non_constant, &non_constant_p, + &overflow_p, constexpr_point_for_decl (init_object)); + + /* If we have created a temporary during this evaluation, its lifetime + will end at the end of the full-expression. The result that refers to + such a temporary would dangle. Lifetime-extended temporaries from + make_temporary_var_for_ref_to_temp were built outside this evaluation. */ + if (!non_constant_p && cxx_dialect >= cxx26 && !TREE_CONSTANT (r)) + { + dangling_data dd = { &global_ctx, init_object }; + if (cp_walk_tree_without_duplicates (&r, find_dangling_ref_r, &dd)) + { + if (!allow_non_constant) + error_at (cp_expr_loc_or_input_loc (t), + "constant evaluation refers to a temporary whose " + "lifetime has ended"); + r = t; + non_constant_p = true; + } + } - /* After verify_constant because reduced_constant_expression_p can unset + /* After verify_constant because constexpr_representable_p can unset CONSTRUCTOR_NO_CLEARING. */ if (TREE_CODE (r) == CONSTRUCTOR && CONSTRUCTOR_NO_CLEARING (r)) { diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 4201eb2d2ab..c3d3bb65c0c 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -9452,7 +9452,6 @@ extern tree fold_non_dependent_init (tree, 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, bool = false); extern bool is_instantiation_of_constexpr (tree); extern bool var_in_constexpr_fn (tree); extern bool var_in_maybe_constexpr_fn (tree); @@ -9470,6 +9469,9 @@ extern void diagnose_failing_condition (tree, location_t, bool, extern bool replace_decl (tree *, tree, tree); extern tree cxa_allocate_and_throw_exception (location_t, const constexpr_ctx *, tree); +extern tree constexpr_point_for_decl (tree); +extern bool constexpr_representable_p (tree, tree); +extern bool valid_static_initializer_p (tree); /* Whether our evaluation wants a prvalue (e.g. CONSTRUCTOR or _CST), a glvalue (e.g. VAR_DECL or _REF), or nothing. */ diff --git a/gcc/cp/init.cc b/gcc/cp/init.cc index e8958051656..b67810d04ea 100644 --- a/gcc/cp/init.cc +++ b/gcc/cp/init.cc @@ -2615,7 +2615,9 @@ constant_value_1 (tree decl, bool strict_p, bool return_aggregate_cst_ok_p, init = instantiate_non_dependent_or_null (init); if (!init || !TREE_TYPE (init) - || !TREE_CONSTANT (init) + || !(TREE_CONSTANT (init) + || (cxx_dialect >= cxx26 + && constexpr_representable_p (init, current_function_decl))) || (!return_aggregate_cst_ok_p /* Unless RETURN_AGGREGATE_CST_OK_P is true, do not return an aggregate constant (of which string @@ -5016,7 +5018,7 @@ build_vec_init (tree base, tree maxindex, tree init, if (!field) field = size_int (num_initialized_elts); tree e = maybe_constant_init (one_init); - if (reduced_constant_expression_p (e)) + if (valid_static_initializer_p (e)) { CONSTRUCTOR_APPEND_ELT (const_vec, field, e); if (do_static_init) @@ -5278,7 +5280,7 @@ build_vec_init (tree base, tree maxindex, tree init, { /* FIXME refs to earlier elts */ tree e = maybe_constant_init (elt_init); - if (reduced_constant_expression_p (e)) + if (valid_static_initializer_p (e)) { if (initializer_zerop (e)) /* Don't fill the CONSTRUCTOR with zeros. */ diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index 9db8f09735f..6a8176ab52a 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -7608,13 +7608,17 @@ invalid_tparm_referent_p (tree type, tree expr, tsubst_flags_t complain) /* Null pointer values are OK in C++11. */; else { + auto_diagnostic_group d; tree c; if (!(complain & tf_error)) ; else if (cxx_dialect >= cxx17 && (c = cxx_constant_value (expr), c == error_mark_node)) - ; + /* cxx_constant_value could emit an error at a different location + than the template argument, so let it be known that the template + argument is where the value was used. */ + inform (input_location, "in template argument for type %qT", type); else if (VAR_P (expr)) error ("%qD is not a valid template argument " "because %qD is a variable, not the address of " @@ -7699,7 +7703,7 @@ get_template_parm_object (tree expr, tree name, bool check_init/*=true*/) { /* The EXPR is the already processed initializer, set it on the NTTP object now so that cp_finish_decl doesn't do it again later. */ - gcc_checking_assert (reduced_constant_expression_p (expr)); + gcc_checking_assert (valid_static_initializer_p (expr)); DECL_INITIAL (decl) = expr; DECL_INITIALIZED_P (decl) = true; DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (decl) = true; @@ -7938,11 +7942,11 @@ convert_nontype_argument (tree type, tree expr, tsubst_flags_t complain) { /* Some assemble time constant expressions like (intptr_t)&&lab1 - (intptr_t)&&lab2 or - 4 + (intptr_t)&&var satisfy reduced_constant_expression_p + 4 + (intptr_t)&&var satisfy valid_static_initializer_p as we can emit them into .rodata initializers of variables, yet they can't fold into an INTEGER_CST at compile time. Refuse them here. */ - gcc_checking_assert (reduced_constant_expression_p (expr)); + gcc_checking_assert (valid_static_initializer_p (expr)); error_at (loc, "template argument %qE for type %qT not " "a compile-time constant", expr, type); return NULL_TREE; @@ -23243,7 +23247,7 @@ tsubst_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl) init = maybe_constant_init (init); if (processing_template_decl ? potential_constant_expression (init) - : reduced_constant_expression_p (init)) + : valid_static_initializer_p (init)) DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (r) = TREE_CONSTANT (r) = true; DECL_INITIAL (r) = init; diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 42b974a764b..426f0ad585d 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -13224,7 +13224,7 @@ cexpr_str::extract (location_t location, const char * &msg, int &len, else { data = maybe_constant_value (message_data, NULL_TREE, mce_true); - if (!reduced_constant_expression_p (data)) + if (!valid_static_initializer_p (data)) data = NULL_TREE; if (len) { diff --git a/gcc/cp/tree.cc b/gcc/cp/tree.cc index 93069475965..7362235948d 100644 --- a/gcc/cp/tree.cc +++ b/gcc/cp/tree.cc @@ -635,7 +635,7 @@ build_target_expr (tree decl, tree value, tsubst_flags_t complain) && !TYPE_HAS_NONTRIVIAL_DESTRUCTOR (type) && !VOID_TYPE_P (TREE_TYPE (value)) && !TYPE_HAS_MUTABLE_P (type) - && reduced_constant_expression_p (value)) + && valid_static_initializer_p (value)) TREE_READONLY (decl) = true; if (complain & tf_no_cleanup) diff --git a/gcc/cp/typeck2.cc b/gcc/cp/typeck2.cc index c7ebd080edc..26270dd01fa 100644 --- a/gcc/cp/typeck2.cc +++ b/gcc/cp/typeck2.cc @@ -1115,7 +1115,8 @@ store_init_value (tree decl, tree init, vec<tree, va_gc>** cleanups, int flags) value = fold_non_dependent_init (value, tf_warning_or_error, /*manifestly_const_eval=*/true, decl); poison_mutable_constructors (value); - const_init = (reduced_constant_expression_p (value) + tree point = constexpr_point_for_decl (decl); + const_init = (constexpr_representable_p (value, point) || error_operand_p (value)); DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (decl) = const_init; /* FIXME setting TREE_CONSTANT on refs breaks the back end. */ @@ -1189,7 +1190,14 @@ store_init_value (tree decl, tree init, vec<tree, va_gc>** cleanups, int flags) && !processing_template_decl && (TREE_SIDE_EFFECTS (value) || vla_type_p (type) - || ! reduced_constant_expression_p (value))) + /* For statics this is about codegen and not about constexpr rules. + But automatic variables in C++26 follow the constexpr rules so + that an initializer containing the address of a local stays in + DECL_INITIAL and thus DECL is constant-initialized. */ + || (TREE_STATIC (decl) + ? !valid_static_initializer_p (value) + : !constexpr_representable_p (value, + constexpr_point_for_decl (decl))))) return split_nonconstant_init (decl, value); /* DECL may change value; purge caches. */ diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-initlist11a.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-initlist11a.C index 2597c3836ed..758b0fb6db3 100644 --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-initlist11a.C +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-initlist11a.C @@ -4,7 +4,10 @@ #include <initializer_list> template <class> void b() { - constexpr std::initializer_list<int> l { 42 }; // { dg-error "" } + constexpr std::initializer_list<int> l { 42 }; // { dg-error "" "" { target c++23_down } } +#if __cplusplus >= 202400L + static_assert (*l.begin () == 42 && l.size () == 1); +#endif } int main() { b<int>(); } diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref12.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref12.C index f4500144946..70de62362ed 100644 --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref12.C +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref12.C @@ -36,9 +36,9 @@ void f(a ap, a& arp) // ** it is usable in constant expressions or // ** its lifetime began within the evaluation of e; - static_assert (ar2.g(),""); // { dg-error "constant" } - static_assert (g(ar2),""); // { dg-error "constant" } - static_assert (h(ar2),""); // { dg-error "constant" } + static_assert (ar2.g(),""); // { dg-error "constant" "" { target c++23_down } } + static_assert (g(ar2),""); // { dg-error "constant" "" { target c++23_down } } + static_assert (h(ar2),""); // { dg-error "constant" "" { target c++23_down } } static_assert (arp.g(),""); static_assert (g(arp),""); diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C index d5327c2dcb2..d14087bfb58 100644 --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ref2.C @@ -10,8 +10,8 @@ constexpr int& ri2 = er; void f(int j) { constexpr int i = 42; - constexpr int const& ri = i; // { dg-error "" } + constexpr int const& ri = i; // { dg-error "" "" { target c++23_down } } - constexpr int& rj = j; // { dg-error "" } + constexpr int& rj = j; // { dg-error "" "" { target c++23_down } } } diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-constexpr3a.C b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-constexpr3a.C index 398e079f724..49303f35ca6 100644 --- a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-constexpr3a.C +++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-constexpr3a.C @@ -6,7 +6,7 @@ struct A { int *const &r; }; void foo (int x) { - constexpr A a = { &x }; // { dg-error "constant" } - static_assert (a.r == &x, ""); // { dg-error "non-constant" } - [&] { static_assert (a.r != nullptr, ""); } (); // { dg-error "non-constant" } + constexpr A a = { &x }; // { dg-error "constant" "" { target c++23_down } } + static_assert (a.r == &x, ""); // { dg-error "non-constant" "" { target c++23_down } } + [&] { static_assert (a.r != nullptr, ""); } (); // { dg-error "non-constant|not usable" } } diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6a.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6a.C index bb844b952e2..69b634a9fdd 100644 --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6a.C +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6a.C @@ -18,8 +18,8 @@ constexpr A bar() void baz() { - constexpr A a = foo(); // { dg-error ".A..& a... is not a constant expression" } - constexpr A b = bar(); // { dg-error ".A..& b... is not a constant expression" } + constexpr A a = foo(); // { dg-error ".A..& a... is not a constant expression" "" { target c++23_down } } + constexpr A b = bar(); // { dg-error ".A..& b... is not a constant expression" "" { target c++23_down } } } constexpr A a = foo(); diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6b.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6b.C index f847fe9809f..2a6535fefec 100644 --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6b.C +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-nsdmi6b.C @@ -19,8 +19,8 @@ constexpr A bar() void baz() { - constexpr A a = foo(); // { dg-error ".A..& a... is not a constant expression" } - constexpr A b = bar(); // { dg-error ".A..& b... is not a constant expression" } + constexpr A a = foo(); // { dg-error ".A..& a... is not a constant expression" "" { target c++23_down } } + constexpr A b = bar(); // { dg-error ".A..& b... is not a constant expression" "" { target c++23_down } } } constexpr A a = foo(); diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-ref1.C b/gcc/testsuite/g++.dg/cpp26/constexpr-ref1.C new file mode 100644 index 00000000000..18dc3bf6549 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-ref1.C @@ -0,0 +1,81 @@ +// PR c++/117784 +// C++26 P2686R5 - references to constexpr variables +// { dg-do compile { target c++26 } } + +void +foo () +{ + constexpr int a = 1; + constexpr auto *b = &a; + static_assert (*b == 1 && b == &a); + static int c = 2; + static constexpr int &d = c; + static_assert (&d == &c); + int e = 3; + constexpr int &f = e; + static_assert (&f == &e); + constexpr const int &r = 42; + static_assert (r == 42); +} + +struct A { int a; const int &b; }; + +void +bar () +{ + static int c; + int d; + constexpr A e = { 1, 2 }; + static constexpr A f = { 3, 4 }; + constexpr const int *g[] = { &c, &d, &e.a, &f.a, &e.b, &f.b }; + static_assert (g[0] == &c && g[1] == &d && g[2] == &e.a + && g[3] == &f.a && g[4] == &e.b && g[5] == &f.b); + auto h = [] { + int i; + constexpr const int *j[] = { &c, &f.a, &i, &f.b }; + static_assert (j[0] == &c && j[1] == &f.a && j[2] == &i && j[3] == &f.b); + }; +} + +constexpr bool +baz () +{ + int x = 0; + int *p = &x; + return p == &x && *p == 0; +} + +static_assert (baz ()); + +void +qux () +{ + int x; + constexpr int *p = &x; + // function parameter scopes associated with a requirement-parameter-list + // are skipped. + static_assert (requires (int t) { requires p != nullptr; }); +} + +template<typename T> +void +f () +{ + constexpr T a = 1; + constexpr auto *p = &a; + static_assert (p == &a); +} +template void f<int> (); + +void +strs () +{ + constexpr const char *p = "abc"; + constexpr const char *q = &"abc"[1]; + constexpr const char (&r)[4] = "abc"; + static_assert (p[0] == 'a' && *q == 'b' && r[2] == 'c'); + [] { + constexpr const char *lp = "abc"; + static_assert (*lp == 'a'); + }; +} diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-ref10.C b/gcc/testsuite/g++.dg/cpp26/constexpr-ref10.C new file mode 100644 index 00000000000..f44e033792a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-ref10.C @@ -0,0 +1,22 @@ +// PR c++/117784 +// C++26 P2686R5 - references to constexpr variables +// { dg-do run { target c++26 } } +// Taken from nsdmi-aggr3.C. + +struct B; +struct A { B *b; }; +struct B { A a{ this }; }; + +[[gnu::noipa]] void +foo () +{ + auto b = B{}; + if (b.a.b != &b) + __builtin_abort (); +} + +int +main () +{ + foo (); +} diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-ref2.C b/gcc/testsuite/g++.dg/cpp26/constexpr-ref2.C new file mode 100644 index 00000000000..adefcbc1d1a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-ref2.C @@ -0,0 +1,28 @@ +// PR c++/117784 +// C++26 P2686R5 - references to constexpr variables +// { dg-do compile { target c++26 } } + +struct A { int a; const int &b; }; + +void +fn () +{ + A a = { 1, 2 }; + constexpr const int *p = &a.b; // { dg-error "the value of .a. is not usable in a constant expression" } + [] { constexpr const int *lp = &a.b; }; // { dg-error ".a. is not captured" } + static A b = { 3, 4 }; + constexpr const int *q = &b.b; // { dg-error "the value of .b. is not usable in a constant expression" } + + int x; + static constexpr int &rx = x; // { dg-error ".x. is not a constant expression" } + + constexpr int i = 1; + static constexpr const int *pi = &i; // { dg-error "not a constant expression" } + [] { constexpr const int *pi2 = &i; }; // { dg-error ".i. is not captured" } + + thread_local int tx; + constexpr int *pt = &tx; // { dg-error "not a constant expression" } + + int ax; + static constinit int &rsa = ax; // { dg-error "not a constant expression|does not have a constant initializer" } +} diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-ref3.C b/gcc/testsuite/g++.dg/cpp26/constexpr-ref3.C new file mode 100644 index 00000000000..d97ed98b31a --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-ref3.C @@ -0,0 +1,15 @@ +// PR c++/117784 +// C++26 P2686R5 - references to constexpr variables +// { dg-do compile { target c++26 } } + +template <int &r> struct S {}; +template <int *p> struct P {}; + +void g () +{ + int x; + constexpr int &r = x; + S<r> s; // { dg-error "not a valid template argument" } + constexpr int *px = &x; // { dg-error "not a constant expression" } + P<px> t; // { dg-message "in template argument for type" } +} diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-ref4.C b/gcc/testsuite/g++.dg/cpp26/constexpr-ref4.C new file mode 100644 index 00000000000..5a362b00875 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-ref4.C @@ -0,0 +1,27 @@ +// PR c++/117784 +// C++26 P2686R5 - references to constexpr variables +// { dg-do run { target c++26 } } + +[[gnu::noipa]] void +check (const int *p, const int *q) +{ + if (p != q || *p != 1) + __builtin_abort (); +} + +[[gnu::noipa]] void +f (int n) +{ + constexpr int a = 1; + constexpr auto *p1 = &a; + check (p1, &a); + if (n > 0) + f (n - 1); + check (p1, &a); +} + +int +main () +{ + f (3); +} diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-ref5.C b/gcc/testsuite/g++.dg/cpp26/constexpr-ref5.C new file mode 100644 index 00000000000..ab047ad1624 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-ref5.C @@ -0,0 +1,34 @@ +// PR c++/117784 +// C++26 P2686R5 - references to constexpr variables +// { dg-do compile { target c++26 } } + +struct S { const int *p; const int &q; constexpr S (const int &x) : p (&x), q (x) {} }; +struct T { const int *p; constexpr T (const int &x) : p (&x) {} }; + +void +f () +{ + constexpr S s (42); // { dg-error "constant evaluation refers to a temporary whose lifetime has ended" } + constexpr T t (42); // { dg-error "constant evaluation refers to a temporary whose lifetime has ended" } +} + +struct A { int a; const int &b; }; + +void +g () +{ + constexpr A e1 = { 1, 2 }; + constexpr A e2 (1, 2); // { dg-error "constant evaluation refers to a temporary whose lifetime has ended" } +} + +struct X { + X *self; + constexpr X () : self (this) {} +}; + +void +h () +{ + constexpr X x; + static_assert (x.self == &x); +} diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-ref6.C b/gcc/testsuite/g++.dg/cpp26/constexpr-ref6.C new file mode 100644 index 00000000000..0f78abfc7ec --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-ref6.C @@ -0,0 +1,96 @@ +// PR c++/117784 +// C++26 P2686R5 - references to constexpr variables +// { dg-do compile { target c++26 } } + +// [expr.const.init] Example 1 +struct A { + int m; + const int& r; +}; +void f() { + static int sx; + thread_local int tx; // tx is never constexpr-referenceable + int ax; + A aa = {1, 2}; + static A sa = {3, 4}; + constexpr A caa = {5, 6}; + static constexpr A csa = {7, 8}; + constexpr auto *p1 = &tx; // { dg-error "not a constant expression" } + constexpr auto &r1 = tx; // { dg-error "not a constant expression" } + // The objects sx, ax, and aa.m, sa.m, and the temporaries to + // which aa.r and sa.r are bound, are constexpr-referenceable. + constexpr auto *p2 = &sx; + constexpr auto &r2 = sx; + constexpr auto *p3 = &ax; + constexpr auto &r3 = ax; + constexpr auto *p4 = &aa.m; + constexpr auto &r4 = aa.m; + constexpr auto *p5 = &sa.m; + constexpr auto &r5 = sa.m; + // aa/sa aren't constexpr so not usable in a constant expression + constexpr auto *p6 = &aa.r; // { dg-error "not usable in a constant expression" } + constexpr auto &r6 = aa.r; // { dg-error "not usable in a constant expression" } + constexpr auto *p7 = &sa.r; // { dg-error "not usable in a constant expression" } + constexpr auto &r7 = sa.r; // { dg-error "not usable in a constant expression" } + constexpr auto *p8 = &caa.m; + constexpr auto &r8 = caa.m; + constexpr auto *p9 = &csa.m; + constexpr auto &r9 = csa.m; + constexpr auto *p10 = &caa.r; + constexpr auto &r10 = caa.r; + constexpr auto *p11 = &csa.r; + constexpr auto &r11 = csa.r; + auto lambda = [] { + int ay; + // The objects sx, sa.m, and ay (but not ax or aa), and the + // temporary to which sa.r is bound, are constexpr-referenceable. + constexpr auto *p12 = &sx; + constexpr auto &r12 = sx; + constexpr auto *p13 = &sa.m; + constexpr auto &r13 = sa.m; + constexpr auto *p14 = &ay; + constexpr auto &r14 = ay; + constexpr auto *p15 = &ax; // { dg-error "not captured" } + constexpr auto &r15 = ax; // { dg-error "not captured" } + constexpr auto *p16 = &aa; // { dg-error "not captured" } + constexpr auto &r16 = aa; // { dg-error "not captured" } + constexpr auto *p17 = &sa.r; // { dg-error "not usable in a constant expression" } + constexpr auto &r17 = sa.r; // { dg-error "not usable in a constant expression" } + constexpr auto *p18 = &csa.r; + constexpr auto &r18 = csa.r; + constexpr auto *p19 = &caa.r; // { dg-error "not captured" } + constexpr auto &r19 = caa.r; // { dg-error "not captured" } + }; +} + +// [expr.const.init] Example 2 +void g() { + int ax = 0; // ax is constant-initialized + thread_local int tx = 0; // tx is constant-initialized + static int sx; // sx is not constant-initialized + static int& rss = sx; // rss is constant-initialized + static int& rst = tx; // rst is not constant-initialized + static int& rsa = ax; // rsa is not constant-initialized + thread_local int& rts = sx; // rts is constant-initialized + thread_local int& rtt = tx; // rtt is not constant-initialized + thread_local int& rta = ax; // rta is not constant-initialized + int& ras = sx; // ras is constant-initialized + int& rat = tx; // rat is not constant-initialized + int& raa = ax; // raa is constant-initialized +} + +// [expr.const.init] Example 3 +struct A2 { + int* const & r; +}; +void h(int x) { + constexpr A2 a = {&x}; + static int sx; + constexpr A2 a2 = {&sx}; + static_assert(a.r == &x); + static_assert(a2.r == &sx); + [&] { + static_assert(a.r != nullptr); // { dg-error "not usable|non-constant condition" } + static_assert(a2.r != nullptr); // { dg-error "not usable|non-constant condition" } + }(); +} diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-ref7.C b/gcc/testsuite/g++.dg/cpp26/constexpr-ref7.C new file mode 100644 index 00000000000..df215ee3877 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-ref7.C @@ -0,0 +1,27 @@ +// PR c++/117784 +// C++26 P2686R5 - references to constexpr variables +// { dg-do compile { target c++26 } } + +// [basic.def.odr] +int f(int); +int g(int&); +struct A { + int x; +}; +struct B { + int& r; +}; +int h(bool cond) { + constexpr A a = {1}; + constexpr const volatile A& r = a; // odr-uses a + int _ = f(cond ? a.x : r.x); // does not odr-use a or r + int x, y; + constexpr B b1 = {x}, b2 = {y}; // odr-uses x and y + int _ = g(cond ? b1.r : b2.r); // does not odr-use b1 or b2 + int _ = ((cond ? x : y), 0); // does not odr-use x or y + return [] { + return b1.r; // { dg-error ".b1. is not captured" } + // error: b1 is odr-used here because the object + // referred to by b1.r is not constexpr-referenceable here + }(); +} diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-ref8.C b/gcc/testsuite/g++.dg/cpp26/constexpr-ref8.C new file mode 100644 index 00000000000..6c76b456dd2 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-ref8.C @@ -0,0 +1,30 @@ +// PR c++/117784 +// C++26 P2686R5 - references to constexpr variables +// { dg-do run { target c++26 } } + +[[gnu::noipa]] int +f (int n) +{ + int i = n; + constexpr const int &ref = i; + auto l = [&] { return ref; }; + i = n + 1; + return l (); +} + +[[gnu::noipa]] int +g (int n) +{ + int i = n; + constexpr const int &ref = i; + auto l = [=] { return ref; }; + i = n + 1; + return l (); +} + +int +main () +{ + if (f (41) != 42 || g (41) != 41) + __builtin_abort (); +} diff --git a/gcc/testsuite/g++.dg/cpp26/constexpr-ref9.C b/gcc/testsuite/g++.dg/cpp26/constexpr-ref9.C new file mode 100644 index 00000000000..8c46dd20fe6 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/constexpr-ref9.C @@ -0,0 +1,28 @@ +// PR c++/117784 +// C++26 P2686R5 - references to constexpr variables +// { dg-do compile { target c++26 } } + +struct B { const int *p; }; +struct C { const B &b; }; +static int y; +constexpr C c1 = { B { &y } }; +static_assert (c1.b.p == &y); // { dg-bogus "" "PR101557" { xfail *-*-* } } + +void +g () +{ + int y; + // This is ill-formed: c.b is bound to a lifetime-extended temporary B{&y}, + // so the temporary's constituent value &y is a constituent value of c too; + // c is static, so it must be constexpr-representable at the following + // namespace-scope point, from which the local y isn't constexpr-referenceable. + static constexpr C c = { B { &y } }; // { dg-error "not a constant expression" } +} + +void +h () +{ + int y; + constexpr C c = { B { &y } }; + static_assert (c.b.p == &y); // { dg-bogus "" "PR101557" { xfail *-*-* } } +} diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt22.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt22.C index b0558d0d96d..c96ecb428e4 100644 --- a/gcc/testsuite/g++.dg/cpp26/expansion-stmt22.C +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt22.C @@ -12,5 +12,5 @@ foo () ; A c {}; template for (constexpr auto a : c) // { dg-warning "'template for' only available with" "" { target c++23_down } } - ; // { dg-error "'c' is not a constant expression" "" { target *-*-* } .-1 } + ; // { dg-error "'c' is not a constant expression" "" { target c++23_down } .-1 } } diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt46.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt46.C new file mode 100644 index 00000000000..88e770677d3 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt46.C @@ -0,0 +1,14 @@ +// PR c++/117784 +// { dg-do compile { target c++26 } } +// Like expansion-stmt22.C but invalid even in C++26 because it +// reads c.i. + +struct A { int i; }; + +void +foo () +{ + A c {}; + template for (constexpr auto a : c) + ; // { dg-error "the value of .c. is not usable in a constant expression" } +} diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class64.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class64.C index eb6489238f0..6813a0e2616 100644 --- a/gcc/testsuite/g++.dg/cpp2a/nontype-class64.C +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class64.C @@ -13,7 +13,7 @@ C<{ 42 }> c1; // OK struct J1 { J1 *self=this; }; -B<J1{}> j1; // { dg-error "not a constant expression" } +B<J1{}> j1; // { dg-error "not a constant expression|constant evaluation refers" } struct J2 { J2 *self=this; diff --git a/gcc/testsuite/g++.dg/cpp2a/nontype-class68.C b/gcc/testsuite/g++.dg/cpp2a/nontype-class68.C index ade646e391b..e6945b23fd3 100644 --- a/gcc/testsuite/g++.dg/cpp2a/nontype-class68.C +++ b/gcc/testsuite/g++.dg/cpp2a/nontype-class68.C @@ -14,11 +14,11 @@ C<{ 42 }> c1; // OK struct J1 { J1 *self = this; }; -B<J1{}> j1; // { dg-error "not a constant expression" } +B<J1{}> j1; // { dg-error "not a constant expression|constant evaluation refers" } struct J2 { J2 *self = this; constexpr J2() {} constexpr J2(const J2&) {} }; -B<J2{}> j2; // { dg-error "not a constant expression" } +B<J2{}> j2; // { dg-error "not a constant expression|constant evaluation refers" } diff --git a/gcc/testsuite/g++.dg/diagnostic/constexpr3.C b/gcc/testsuite/g++.dg/diagnostic/constexpr3.C index 39ddf01a73c..5beb3b1f84b 100644 --- a/gcc/testsuite/g++.dg/diagnostic/constexpr3.C +++ b/gcc/testsuite/g++.dg/diagnostic/constexpr3.C @@ -18,16 +18,16 @@ foo () void test () { - constexpr int i = 42; // { dg-message "may differ on each invocation" } + constexpr int i = 42; // { dg-message "may differ on each invocation" "" { target c++23_down } } - constexpr X x{i}; // { dg-error "not a constant expression" } - // { dg-message "reference to .i. is not a constant expression" "" { target *-*-* } .-1 } - constexpr const int *p = &i; // { dg-error "not a constant expression" } - // { dg-message "pointer to .i. is not a constant expression" "" { target *-*-* } .-1 } + constexpr X x{i}; // { dg-error "not a constant expression" "" { target c++23_down } } + // { dg-message "reference to .i. is not a constant expression" "" { target c++23_down } .-1 } + constexpr const int *p = &i; // { dg-error "not a constant expression" "" { target c++23_down } } + // { dg-message "pointer to .i. is not a constant expression" "" { target c++23_down } .-1 } - constexpr A a = foo (); // { dg-error "not a constant expression" } - // { dg-message "pointer to .a. is not a constant expression|may differ" "" { target *-*-* } .-1 } + constexpr A a = foo (); // { dg-error "not a constant expression" "" { target c++23_down } } + // { dg-message "pointer to .a. is not a constant expression|may differ" "" { target c++23_down } .-1 } - constexpr const int *q = __builtin_launder (&i); // { dg-error "not a constant expression" } - // { dg-message "pointer to .i. is not a constant expression" "" { target *-*-* } .-1 } + constexpr const int *q = __builtin_launder (&i); // { dg-error "not a constant expression" "" { target c++23_down } } + // { dg-message "pointer to .i. is not a constant expression" "" { target c++23_down } .-1 } } diff --git a/gcc/testsuite/g++.dg/reflect/value1.C b/gcc/testsuite/g++.dg/reflect/value1.C index 1d6d7d59098..3f2c4411d6f 100644 --- a/gcc/testsuite/g++.dg/reflect/value1.C +++ b/gcc/testsuite/g++.dg/reflect/value1.C @@ -287,15 +287,7 @@ g (bool b) CB lcb3(^^int); // { dg-error "initialized with a consteval-only value" } Wrapper lw1{ ^^int }; // { dg-error "initialized with a consteval-only value" } - // w2 at block scope has this initializer: - // {.r=(<<< Unknown tree: lang_type >>> &) &_ZGRL2w2_} - // which is reduced_constant_expression_p but here we have - // {.r=(<<< Unknown tree: lang_type >>> &) &D.2718} - // which is not reduced_constant_expression_p. The error - // also happens without reflection: - // struct W { const int &r; } - // void h () { constexpr W lw{42}; } - constexpr Wrapper lw2{ ^^int }; // { dg-error "not a constant expression" } + constexpr Wrapper lw2{ ^^int }; constexpr N lcng{}; N lng; // { dg-error "initialized with a consteval-only value" } diff --git a/libstdc++-v3/testsuite/20_util/function_ref/dangling_neg.cc b/libstdc++-v3/testsuite/20_util/function_ref/dangling_neg.cc index 4e99f82a50c..3b8791a7682 100644 --- a/libstdc++-v3/testsuite/20_util/function_ref/dangling_neg.cc +++ b/libstdc++-v3/testsuite/20_util/function_ref/dangling_neg.cc @@ -78,7 +78,7 @@ constexpr auto func = [](std::string s) noexcept -> int { return s.size(); }; constexpr std::function_ref<int(const std::string&)> incompatible1FRef - = std::function_ref<int(std::string)>(func); // { dg-error "is not a constant expression" } + = std::function_ref<int(std::string)>(func); // { dg-error "constant evaluation refers to a temporary whose lifetime has ended" } constexpr std::function_ref<long(std::string)> incompatible2FRef - = std::function_ref<int(std::string)>(func); // { dg-error "is not a constant expression" } + = std::function_ref<int(std::string)>(func); // { dg-error "constant evaluation refers to a temporary whose lifetime has ended" } base-commit: b922224de8e06650f86df59143b17c4df0c08381 -- 2.56.0
