On Wed, Feb 11, 2026 at 02:15:54PM +0100, Jakub Jelinek wrote: > On Wed, Feb 11, 2026 at 08:58:26PM +0900, Jason Merrill wrote: > > We might replace that check with something separate in cp_finish_decl, but > > your approach seems reasonable. > > > > I'd drop constinit, that doesn't seem to qualify under > > [basic.types.general]/12.1. > > You're right. Seems constinit was still in P2996R9 but P2996R10 > has removed it except for one occurrence in the revision history (that > is ok, but surprisingly the P2996R10 change that removed it is not > mentioned). > > So what about this updated patch then?
Marek made me to grep further and I found one more constinit reference in a comment, so here is another version of the patch with that fixed, this time bootstrapped/regtested on x86_64-linux and i686-linux successfully. 2026-02-12 Jakub Jelinek <[email protected]> PR c++/124012 * reflect.cc (check_out_of_consteval_use_r): New function. (check_out_of_consteval_use): Use it instead of lambda. Don't ignore constexpr/constinit vars in the walker and walk DECL_VALUE_EXPR of vars which have it. Ignore expr equal to constexpr VAR_DECL. In diagnostics only complain about missing constexpr on VAR_DECLs without that flag and never suggest constinit. Remove constinit traces from function comment. * g++.dg/reflect/pr124012.C: New test. * g++.dg/reflect/init1.C (r): Change constinit to constexpr. (p): Change constinit to constexpr const. * g++.dg/reflect/init6.C: Expect diagnostics for constinit consteval-only vars. * g++.dg/reflect/init7.C: Likewise. * g++.dg/reflect/init10.C: Likewise. * g++.dg/reflect/diag3.C: Likewise. Don't expect suggestion to add constinit. --- gcc/cp/reflect.cc.jj 2026-02-11 11:21:41.800645517 +0100 +++ gcc/cp/reflect.cc 2026-02-11 16:10:47.583625789 +0100 @@ -8092,8 +8092,79 @@ consteval_only_p (tree t) return !!cp_walk_tree (&t, consteval_only_type_r, &visited, &visited); } +/* A walker for check_out_of_consteval_use_r. It cannot be a lambda, because + we have to call this recursively. */ + +static tree +check_out_of_consteval_use_r (tree *tp, int *walk_subtrees, void *pset) +{ + tree t = *tp; + + /* No need to look into types or unevaluated operands. */ + if (TYPE_P (t) + || unevaluated_p (TREE_CODE (t)) + /* Don't walk INIT_EXPRs, because we'd emit bogus errors about + member initializers. */ + || TREE_CODE (t) == INIT_EXPR + /* Don't walk BIND_EXPR_VARS. */ + || TREE_CODE (t) == BIND_EXPR + /* And don't recurse on DECL_EXPRs. */ + || TREE_CODE (t) == DECL_EXPR) + { + *walk_subtrees = false; + return NULL_TREE; + } + + /* A subexpression of a manifestly constant-evaluated expression is + an immediate function context. For example, + + consteval void foo (std::meta::info) { } + void g() { foo (^^void); } + + is all good. */ + if (tree decl = cp_get_callee_fndecl_nofold (t)) + if (immediate_invocation_p (decl)) + { + *walk_subtrees = false; + return NULL_TREE; + } + + if (VAR_P (t) && DECL_HAS_VALUE_EXPR_P (t)) + { + tree vexpr = DECL_VALUE_EXPR (t); + if (tree ret = cp_walk_tree (&vexpr, check_out_of_consteval_use_r, pset, + (hash_set<tree> *) pset)) + return ret; + } + + /* Now check the type to see if we are dealing with a consteval-only + expression. */ + if (!consteval_only_p (t)) + return NULL_TREE; + + /* Already escalated? */ + if (current_function_decl + && DECL_IMMEDIATE_FUNCTION_P (current_function_decl)) + { + *walk_subtrees = false; + return NULL_TREE; + } + + /* We might have to escalate if we are in an immediate-escalating + function. */ + if (immediate_escalating_function_p (current_function_decl)) + { + promote_function_to_consteval (current_function_decl); + *walk_subtrees = false; + return NULL_TREE; + } + + *walk_subtrees = false; + return t; +} + /* Detect if a consteval-only expression EXPR or a consteval-only - variable EXPR not declared constexpr/constinit is used outside + variable EXPR not declared constexpr is used outside a manifestly constant-evaluated context. E.g.: void f() { @@ -8115,88 +8186,24 @@ consteval_only_p (tree t) bool check_out_of_consteval_use (tree expr, bool complain/*=true*/) { - if (!flag_reflection || in_immediate_context ()) + if (!flag_reflection || in_immediate_context () || expr == NULL_TREE) return false; - auto walker = [](tree *tp, int *walk_subtrees, void *) -> tree - { - tree t = *tp; - - /* No need to look into types or unevaluated operands. */ - if (TYPE_P (t) - || unevaluated_p (TREE_CODE (t)) - /* Don't walk INIT_EXPRs, because we'd emit bogus errors about - member initializers. */ - || TREE_CODE (t) == INIT_EXPR - /* Don't walk BIND_EXPR_VARS. */ - || TREE_CODE (t) == BIND_EXPR - /* And don't recurse on DECL_EXPRs. */ - || TREE_CODE (t) == DECL_EXPR) - { - *walk_subtrees = false; - return NULL_TREE; - } - - /* A subexpression of a manifestly constant-evaluated expression is - an immediate function context. For example, - - consteval void foo (std::meta::info) { } - void g() { foo (^^void); } - - is all good. */ - if (tree decl = cp_get_callee_fndecl_nofold (t)) - if (immediate_invocation_p (decl)) - { - *walk_subtrees = false; - return NULL_TREE; - } - - if (VAR_P (t) - && (DECL_DECLARED_CONSTEXPR_P (t) || DECL_DECLARED_CONSTINIT_P (t))) - /* This is fine, don't bother checking the type. */ - return NULL_TREE; - - /* Now check the type to see if we are dealing with a consteval-only - expression. */ - if (!consteval_only_p (t)) - return NULL_TREE; - - /* Already escalated? */ - if (current_function_decl - && DECL_IMMEDIATE_FUNCTION_P (current_function_decl)) - { - *walk_subtrees = false; - return NULL_TREE; - } - - /* We might have to escalate if we are in an immediate-escalating - function. */ - if (immediate_escalating_function_p (current_function_decl)) - { - promote_function_to_consteval (current_function_decl); - *walk_subtrees = false; - return NULL_TREE; - } - - *walk_subtrees = false; - return t; - }; + if (VAR_P (expr) && DECL_DECLARED_CONSTEXPR_P (expr)) + return false; - if (tree t = cp_walk_tree_without_duplicates (&expr, walker, nullptr)) + hash_set<tree> pset; + if (tree t = cp_walk_tree (&expr, check_out_of_consteval_use_r, &pset, &pset)) { if (complain) { - if (VAR_P (t)) + if (VAR_P (t) && !DECL_DECLARED_CONSTEXPR_P (t)) { auto_diagnostic_group d; error_at (cp_expr_loc_or_input_loc (t), "consteval-only variable %qD not declared %<constexpr%> " "used outside a constant-evaluated context", t); - if (TREE_STATIC (t) || CP_DECL_THREAD_LOCAL_P (t)) - inform (DECL_SOURCE_LOCATION (t), "add %<constexpr%> or " - "%<constinit%>"); - else - inform (DECL_SOURCE_LOCATION (t), "add %<constexpr%>"); + inform (DECL_SOURCE_LOCATION (t), "add %<constexpr%>"); } else error_at (cp_expr_loc_or_input_loc (t), --- gcc/testsuite/g++.dg/reflect/pr124012.C.jj 2026-02-11 13:41:43.184381452 +0100 +++ gcc/testsuite/g++.dg/reflect/pr124012.C 2026-02-11 13:41:43.184381452 +0100 @@ -0,0 +1,44 @@ +// PR c++/124012 +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } + +void foo (char); +void corge (const char *); +struct A { char a; decltype (^^::) b; }; + +void +bar () +{ + constexpr auto [a, b] = A {}; + foo (a); // { dg-error "consteval-only expressions are only allowed in a constant-evaluated context" } +} + +void +baz () +{ + constexpr auto a = A {}; + foo (a.a); // { dg-error "consteval-only expressions are only allowed in a constant-evaluated context" } +} + +void +qux () +{ + constexpr auto a = A {}; + corge (&a.a); // { dg-error "consteval-only expressions are only allowed in a constant-evaluated context" } +} + +void +garply () +{ + constexpr auto [a, b] = A {}; + corge (&a); // { dg-error "consteval-only expressions are only allowed in a constant-evaluated context" } +} + +void +fred () +{ + constexpr auto [a, b] = A {}; + constexpr auto c = a; + foo (c); + corge (&c); +} --- gcc/testsuite/g++.dg/reflect/init1.C.jj 2026-01-15 16:33:47.009097925 +0100 +++ gcc/testsuite/g++.dg/reflect/init1.C 2026-02-11 13:58:21.056264055 +0100 @@ -22,8 +22,8 @@ struct W { decltype(^^::) i = ^^W::i; }; -constinit info r = ^^int; -constinit info *p = &r; +constexpr info r = ^^int; +constexpr const info *p = &r; consteval void f () --- gcc/testsuite/g++.dg/reflect/init6.C.jj 2026-01-15 16:33:47.009097925 +0100 +++ gcc/testsuite/g++.dg/reflect/init6.C 2026-02-11 13:53:32.472078878 +0100 @@ -12,11 +12,11 @@ struct N { }; S s1; // { dg-error "consteval-only variable" } -constinit S s2{}; +constinit S s2{}; // { dg-error "consteval-only variable" } constexpr S s3{^^int}; N n1; // { dg-error "consteval-only variable" } -constinit N n2; +constinit N n2; // { dg-error "consteval-only variable" } constexpr N n3; template<typename T> @@ -25,7 +25,7 @@ struct X { }; X<info> x1; // { dg-error "consteval-only variable" } -constinit X<info> x2{}; +constinit X<info> x2{}; // { dg-error "consteval-only variable" } constexpr X<info> x3{^^int}; void --- gcc/testsuite/g++.dg/reflect/init7.C.jj 2026-01-15 16:33:47.009097925 +0100 +++ gcc/testsuite/g++.dg/reflect/init7.C 2026-02-11 13:52:51.484762719 +0100 @@ -9,7 +9,7 @@ info r1 = ^^int; // { dg-error "constev const info r2 = ^^int; // { dg-error "consteval-only variable .r2. not declared .constexpr. used outside a constant-evaluated context" } constexpr info r3 = ^^int; -constinit info r4 = ^^int; +constinit info r4 = ^^int; // { dg-error "consteval-only variable .r4. not declared .constexpr. used outside a constant-evaluated context" } const info *const p1 = &r3; // { dg-error "consteval-only variable .p1. not declared .constexpr. used outside a constant-evaluated context" } info *p2; // { dg-error "consteval-only variable .p2. not declared .constexpr. used outside a constant-evaluated context" } const info &q = r3; // { dg-error "consteval-only variable .q. not declared .constexpr. used outside a constant-evaluated context" } @@ -23,7 +23,7 @@ g () static info l4 = ^^int; // { dg-error "consteval-only variable .l4. not declared .constexpr. used outside a constant-evaluated context" } static const info l5 = ^^int; // { dg-error "consteval-only variable .l5. not declared .constexpr. used outside a constant-evaluated context" } static constexpr info l6 = ^^int; - static constinit info l7 = ^^int; + static constinit info l7 = ^^int; // { dg-error "consteval-only variable .l7. not declared .constexpr. used outside a constant-evaluated context" } } consteval void --- gcc/testsuite/g++.dg/reflect/init10.C.jj 2026-01-15 16:33:47.009097925 +0100 +++ gcc/testsuite/g++.dg/reflect/init10.C 2026-02-11 13:54:06.441512118 +0100 @@ -10,7 +10,7 @@ struct A { }; A a1; // { dg-error "consteval-only variable .a1." } -constinit A a2; +constinit A a2; // { dg-error "consteval-only variable .a2." } constexpr A a3; struct B { @@ -20,5 +20,5 @@ struct B { }; B b1; // { dg-error "consteval-only variable .b1." } -constinit B b2; +constinit B b2; // { dg-error "consteval-only variable .b2." } constexpr B b3; --- gcc/testsuite/g++.dg/reflect/diag3.C.jj 2026-01-15 16:33:47.007097959 +0100 +++ gcc/testsuite/g++.dg/reflect/diag3.C 2026-02-11 13:56:51.692755021 +0100 @@ -1,14 +1,16 @@ // { dg-do compile { target c++26 } } // { dg-additional-options "-freflection" } -// Test that we suggest adding "constexpr" or "constinit" (where allowed). +// Test that we suggest adding "constexpr" (where allowed). auto foo = ^^int; // { dg-error "consteval-only variable .foo." } -// { dg-message "add .constexpr. or .constinit." "" { target *-*-* } .-1 } -constinit auto foo_ = ^^int; +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } +constinit auto foo_ = ^^int; // { dg-error "consteval-only variable .foo_." } +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } constexpr auto foo__ = ^^int; thread_local auto tfoo = ^^int; // { dg-error "consteval-only variable .tfoo." } -// { dg-message "add .constexpr. or .constinit." "" { target *-*-* } .-1 } -thread_local constinit auto tfoo_ = ^^int; +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } +thread_local constinit auto tfoo_ = ^^int; // { dg-error "consteval-only variable .tfoo_." } +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } thread_local constexpr auto tfoo__ = ^^int; void @@ -18,11 +20,13 @@ f () // { dg-message "add .constexpr." "" { target *-*-* } .-1 } constexpr auto ref_ = ^^int; static auto sref = ^^int; // { dg-error "consteval-only variable .sref." } -// { dg-message "add .constexpr. or .constinit." "" { target *-*-* } .-1 } - static auto constinit sref_ = ^^int; +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } + static auto constinit sref_ = ^^int; // { dg-error "consteval-only variable .sref_." } +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } static auto constexpr sref__ = ^^int; thread_local auto tref = ^^int; // { dg-error "consteval-only variable .tref." } -// { dg-message "add .constexpr. or .constinit." "" { target *-*-* } .-1 } - thread_local constinit auto tref_ = ^^int; +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } + thread_local constinit auto tref_ = ^^int; // { dg-error "consteval-only variable .tref_." } +// { dg-message "add .constexpr." "" { target *-*-* } .-1 } thread_local constexpr auto tref__ = ^^int; } Jakub
