See <https://wg21.link/p2564>.
A (very) rudimentary patch that handles the simplest case so far: From be2950f8cf3fb5ca0571054b4196de35d9807519 Mon Sep 17 00:00:00 2001 From: Marek Polacek <polacek@redhat.com> Date: Thu, 10 Aug 2023 17:06:11 -0400 Subject: [PATCH] c++: implement P2564, consteval needs to propagate up [PR107687] --- gcc/cp/call.cc | 48 ++++++++++++++++++++ gcc/cp/cp-gimplify.cc | 26 +++++++++-- gcc/testsuite/g++.dg/cpp23/consteval-if10.C | 2 +- gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C | 36 +++++++++++++++ 4 files changed, 108 insertions(+), 4 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C diff --git a/gcc/cp/call.cc b/gcc/cp/call.cc index 673ec91d60e..34b2fe1884d 100644 --- a/gcc/cp/call.cc +++ b/gcc/cp/call.cc @@ -9740,6 +9740,7 @@ build_trivial_dtor_call (tree instance, bool no_ptr_deref) bool in_immediate_context () { + // TODO update to say that manifestly constant eval ctx is an IFC return (cp_unevaluated_operand != 0 || (current_function_decl != NULL_TREE && DECL_IMMEDIATE_FUNCTION_P (current_function_decl)) @@ -9762,6 +9763,34 @@ immediate_invocation_p (tree fn) && !in_immediate_context ()); } +/* Return true if FN is an immediate-escalating function. */ + +static bool +immediate_escalating_function_p (tree fn) +{ + if (!fn) + return false; + + gcc_checking_assert (TREE_CODE (fn) == FUNCTION_DECL); + + /* An immediate-escalating function is + -- the call operator of a lambda that is not declared with the consteval + specifier */ + if (LAMBDA_FUNCTION_P (fn) && !DECL_IMMEDIATE_FUNCTION_P (fn)) + return true; + /* -- a defaulted special member function that is not declared with the + consteval specifier */ + special_function_kind sfk = special_memfn_p (fn); + if (sfk != sfk_none + && DECL_DEFAULTED_FN (fn) + && !DECL_IMMEDIATE_FUNCTION_P (fn)) + return true; + /* -- a function that results from the instantiation of a templated entity + defined with the constexpr specifier. */ + // TODO check if the DECL_DEFAULTED_FN part is actually OK here + return is_instantiation_of_constexpr (fn); +} + /* Subroutine of the various build_*_call functions. Overload resolution has chosen a winning candidate CAND; build up a CALL_EXPR accordingly. ARGS is a TREE_LIST of the unconverted arguments to the call. FLAGS is a @@ -10484,6 +10513,7 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain) tree fndecl = STRIP_TEMPLATE (TREE_OPERAND (fn, 0)); if (immediate_invocation_p (fndecl)) { + tree orig_call = call; tree obj_arg = NULL_TREE; /* Undo convert_from_reference called by build_cxx_call. */ if (REFERENCE_REF_P (call)) @@ -10508,6 +10538,24 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain) obj_arg = TREE_OPERAND (addr, 0); } } + + /* [expr.const]p16 "An expression or conversion is + immediate-escalating if it is not initially in an immediate + function context and it is either + -- an immediate invocation that is not a constant expression and + is not a subexpression of an immediate invocation." + + If we are in an immediate-escalating function, the + immediate-escalating expression or conversion makes it an + immediate function. So CALL does not need to produce a constant + expression. ??? It's ugly to call cxx_constant_value twice in + some cases. */ + if (immediate_escalating_function_p (current_function_decl) + && cxx_constant_value (call, obj_arg, tf_none) == error_mark_node) + { + SET_DECL_IMMEDIATE_FUNCTION_P (current_function_decl); + return orig_call; + } call = cxx_constant_value (call, obj_arg, complain); if (obj_arg && !error_operand_p (call)) call = cp_build_init_expr (obj_arg, call); diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc index 206e791fcfd..5fc0e3cc84b 100644 --- a/gcc/cp/cp-gimplify.cc +++ b/gcc/cp/cp-gimplify.cc @@ -1017,6 +1017,20 @@ maybe_replace_decl (tree *tp, tree decl, tree replacement) return true; } +/* Maybe say that FN was initially not an immediate function, but was + promoted to one because its body contained an immediate-escalating + expression or conversion. */ + +static void +maybe_explain_promoted_consteval (location_t loc, tree fn) +{ + /* A function cannot be marked both constexpr and consteval + except when we've promoted the former to the latter. */ + if (is_instantiation_of_constexpr (fn) + && DECL_DECLARED_CONSTEXPR_P (fn)) + inform (loc, "%qD was promoted to an immediate function", fn); +} + /* Genericization context. */ struct cp_genericize_data @@ -1050,9 +1064,13 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_) && DECL_IMMEDIATE_FUNCTION_P (PTRMEM_CST_MEMBER (stmt))) { if (!data->pset.add (stmt)) - error_at (PTRMEM_CST_LOCATION (stmt), - "taking address of an immediate function %qD", - PTRMEM_CST_MEMBER (stmt)); + { + error_at (PTRMEM_CST_LOCATION (stmt), + "taking address of an immediate function %qD", + PTRMEM_CST_MEMBER (stmt)); + maybe_explain_promoted_consteval (PTRMEM_CST_LOCATION (stmt), + PTRMEM_CST_MEMBER (stmt)); + } stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt)); break; } @@ -1065,6 +1083,8 @@ cp_fold_r (tree *stmt_p, int *walk_subtrees, void *data_) error_at (EXPR_LOCATION (stmt), "taking address of an immediate function %qD", TREE_OPERAND (stmt, 0)); + maybe_explain_promoted_consteval (EXPR_LOCATION (stmt), + TREE_OPERAND (stmt, 0)); stmt = *stmt_p = build_zero_cst (TREE_TYPE (stmt)); break; } diff --git a/gcc/testsuite/g++.dg/cpp23/consteval-if10.C b/gcc/testsuite/g++.dg/cpp23/consteval-if10.C index 4c0523fe1d0..f799e81113d 100644 --- a/gcc/testsuite/g++.dg/cpp23/consteval-if10.C +++ b/gcc/testsuite/g++.dg/cpp23/consteval-if10.C @@ -10,7 +10,7 @@ bar (int x) int r = 0; if consteval // { dg-warning "'if consteval' only available with" "" { target c++20_only } } { - auto y = [=] { foo (x); }; // { dg-error "'x' is not a constant expression" } + auto y = [=] { foo (x); }; y (); } return r; diff --git a/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C new file mode 100644 index 00000000000..9a791b9cf0b --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp2a/consteval-prop1.C @@ -0,0 +1,36 @@ +// P2564R3 +// { dg-do compile { target c++20 } } + +consteval int id(int i) { return i; } + +template <typename T> +constexpr int +f (T t) +{ + // OK, f promoted to consteval. + return id (t); +} + +constexpr auto a1 = f (3); + +// As a consequence of f<int> being promoted to an immediate function, we +// can't take its address. +auto p1 = &f<int>; // { dg-error "taking address of an immediate function" } + +template <typename T> +constexpr int +f2 (T) +{ + // This produces a constant; f2 *not* promoted to consteval. + return id (42); +} + +// ... so we can take its address. +auto p2 = &f2<int>; + +constexpr int +f3 (int i) +{ + // f3 isn't a function template and those don't get upgraded to consteval. + return id (i); // { dg-error "not a constant expression" } +} base-commit: ecfd8c7ffecf9e8f851c996ec149fbda7ef202f5 -- 2.41.0
I don't understand why clang++ doesn't error here: -- consteval int id(int i) { return i; } template<typename T> constexpr int f (T t) { auto p = id; // immediate-escalating expr return t; } auto q = &f<int>; // error? -- I though naming 'id' should promote f<int> to consteval and therefore you can't take its address.
The trunk branch has been updated by Marek Polacek <mpolacek@gcc.gnu.org>: https://gcc.gnu.org/g:1f1c432226cf3db399b2a2a627e3c5720b02b1d6 commit r14-6129-g1f1c432226cf3db399b2a2a627e3c5720b02b1d6 Author: Marek Polacek <polacek@redhat.com> Date: Tue Sep 19 16:31:17 2023 -0400 c++: implement P2564, consteval needs to propagate up [PR107687] This patch implements P2564, described at <wg21.link/p2564>, whereby certain functions are promoted to consteval. For example: consteval int id(int i) { return i; } template <typename T> constexpr int f(T t) { return t + id(t); // id causes f<int> to be promoted to consteval } void g(int i) { f (3); } now compiles. Previously the code was ill-formed: we would complain that 't' in 'f' is not a constant expression. Since 'f' is now consteval, it means that the call to id(t) is in an immediate context, so doesn't have to produce a constant -- this is how we allow consteval functions composition. But making 'f<int>' consteval also means that the call to 'f' in 'g' must yield a constant; failure to do so results in an error. I made the effort to have cc1plus explain to us what's going on. For example, calling f(i) produces this neat diagnostic: w.C:11:11: error: call to consteval function 'f<int>(i)' is not a constant expression 11 | f (i); | ~~^~~ w.C:11:11: error: 'i' is not a constant expression w.C:6:22: note: 'constexpr int f(T) [with T = int]' was promoted to an immediate function because its body contains an immediate-escalating expression 'id(t)' 6 | return t + id(t); // id causes f<int> to be promoted to consteval | ~~^~~ which hopefully makes it clear what's going on. Implementing this proposal has been tricky. One problem was delayed instantiation: instantiating a function can set off a domino effect where one call promotes a function to consteval but that then means that another function should also be promoted, etc. In v1, I addressed the delayed instantiation problem by instantiating trees early, so that we can escalate functions right away. That caused a number of problems, and in certain cases, like consteval-prop3.C, it can't work, because we need to wait till EOF to see the definition of the function anyway. Overeager instantiation tends to cause diagnostic problems too. In v2, I attempted to move the escalation to the gimplifier, at which point all templates have been instantiated. That attempt flopped, however, because once we've gimplified a function, its body is discarded and as a consequence, you can no longer evaluate a call to that function which is required for escalating, which needs to decide if a call is a constant expression or not. Therefore, we have to perform the escalation before gimplifying, but after instantiate_pending_templates. That's not easy because we have no way to walk all the trees. In the v2 patch, I use two vectors: one to store function decls that may become consteval, and another to remember references to immediate-escalating functions. Unfortunately the latter must also stash functions that call immediate-escalating functions. Consider: int g(int i) { f<int>(i); // f is immediate-escalating } where g itself is not immediate-escalating, but we have to make sure that if f gets promoted to consteval, we give an error. A new option, -fno-immediate-escalation, is provided to suppress escalating functions. v2 also adds a new flag, DECL_ESCALATION_CHECKED_P, so that we don't escalate a function multiple times, and so that we can distinguish between explicitly consteval functions and functions that have been promoted to consteval. In v3, I removed one of the new vectors and changed the other one to a hash set. This version also contains numerous cleanups. v4 merges find_escalating_expr_r into cp_fold_immediate_r. It also adds a new optimization in cp_fold_function. v5 greatly simplifies the code. v6 simplifies the code further and removes an ff_ flag. v7 removes maybe_promote_function_to_consteval and further simplifies cp_fold_immediate_r logic. v8 removes maybe_store_immediate_escalating_fn. PR c++/107687 PR c++/110997 gcc/c-family/ChangeLog: * c-cppbuiltin.cc (c_cpp_builtins): Update __cpp_consteval. * c-opts.cc (c_common_post_options): Pre-C++20, unset flag_immediate_escalation. * c.opt (fimmediate-escalation): New option. gcc/cp/ChangeLog: * call.cc (in_immediate_context): No longer static. * constexpr.cc (cxx_eval_call_expression): Adjust assert. * cp-gimplify.cc (deferred_escalating_exprs): New vec. (remember_escalating_expr): New. (enum fold_flags): Remove ff_fold_immediate. (immediate_escalating_function_p): New. (unchecked_immediate_escalating_function_p): New. (promote_function_to_consteval): New. (cp_fold_immediate): Move above. Return non-null if any errors were emitted. (maybe_explain_promoted_consteval): New. (cp_gimplify_expr) <case CALL_EXPR>: Assert we've handled all immediate invocations. (taking_address_of_imm_fn_error): New. (cp_fold_immediate_r): Merge ADDR_EXPR and PTRMEM_CST cases. Implement P2564 - promoting functions to consteval. <case CALL_EXPR>: Implement P2564 - promoting functions to consteval. (cp_fold_r): If an expression turns into a CALL_EXPR after cp_fold, call cp_fold_immediate_r on the CALL_EXPR. (cp_fold_function): Set DECL_ESCALATION_CHECKED_P if deferred_escalating_exprs does not contain current_function_decl. (process_and_check_pending_immediate_escalating_fns): New. * cp-tree.h (struct lang_decl_fn): Add escalated_p bit-field. (DECL_ESCALATION_CHECKED_P): New. (immediate_invocation_p): Declare. (process_pending_immediate_escalating_fns): Likewise. * decl2.cc (c_parse_final_cleanups): Set at_eof to 2 after all templates have been instantiated; and to 3 at the end of the function. Call process_pending_immediate_escalating_fns. * error.cc (dump_template_bindings): Check at_eof against an updated value. * module.cc (trees_out::lang_decl_bools): Stream escalated_p. (trees_in::lang_decl_bools): Likewise. * pt.cc (push_tinst_level_loc): Set at_eof to 3, not 2. * typeck.cc (cp_build_addr_expr_1): Don't check DECL_IMMEDIATE_FUNCTION_P. gcc/ChangeLog: * doc/invoke.texi: Document -fno-immediate-escalation. libstdc++-v3/ChangeLog: * testsuite/18_support/comparisons/categories/zero_neg.cc: Add dg-prune-output. * testsuite/std/format/string_neg.cc: Add dg-error. gcc/testsuite/ChangeLog: * g++.dg/cpp23/consteval-if10.C: Remove dg-error. * g++.dg/cpp23/consteval-if2.C: Likewise. * g++.dg/cpp23/feat-cxx2b.C: Adjust expected value of __cpp_consteval. * g++.dg/cpp26/feat-cxx26.C: Likewise. * g++.dg/cpp2a/consteval-memfn1.C: Add dg-error. * g++.dg/cpp2a/consteval11.C: Likewise. * g++.dg/cpp2a/consteval3.C: Adjust dg-error. * g++.dg/cpp2a/consteval34.C: Add dg-error. * g++.dg/cpp2a/consteval36.C: Likewise. * g++.dg/cpp2a/consteval9.C: Likewise. * g++.dg/cpp2a/feat-cxx2a.C: Adjust expected value of __cpp_consteval. * g++.dg/cpp2a/spaceship-synth9.C: Adjust dg-error. * g++.dg/cpp2a/consteval-prop1.C: New test. * g++.dg/cpp2a/consteval-prop10.C: New test. * g++.dg/cpp2a/consteval-prop11.C: New test. * g++.dg/cpp2a/consteval-prop12.C: New test. * g++.dg/cpp2a/consteval-prop13.C: New test. * g++.dg/cpp2a/consteval-prop14.C: New test. * g++.dg/cpp2a/consteval-prop15.C: New test. * g++.dg/cpp2a/consteval-prop16.C: New test. * g++.dg/cpp2a/consteval-prop17.C: New test. * g++.dg/cpp2a/consteval-prop18.C: New test. * g++.dg/cpp2a/consteval-prop19.C: New test. * g++.dg/cpp2a/consteval-prop20.C: New test. * g++.dg/cpp2a/consteval-prop2.C: New test. * g++.dg/cpp2a/consteval-prop3.C: New test. * g++.dg/cpp2a/consteval-prop4.C: New test. * g++.dg/cpp2a/consteval-prop5.C: New test. * g++.dg/cpp2a/consteval-prop6.C: New test. * g++.dg/cpp2a/consteval-prop7.C: New test. * g++.dg/cpp2a/consteval-prop8.C: New test. * g++.dg/cpp2a/consteval-prop9.C: New test.
Implemented in GCC 14.