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?
> > There is an unresolved part of the PR, if there is
> > constexpr auto a = A {};
> > int b = a.a;
> > then we don't reject that (similarly after the patch with constexpr
> > structured binding). The problem in that case is that we try to
> > constant evaluate initializers of vars (and a few other spots) and
> > if they fold to constants (like in this case to 0) even when it is
> > not manifestly constant-evaluated, we just replace it with the constant
> > and so don't see there was a consteval-only use that should have
> > been reported. Of course, if it is something manifestly constant-evaluated
> > and folds into constant, it shouldn't be rejected.
> > So I wonder if we don't need to call check_out_of_consteval_use in
> > further spots...
>
> Perhaps we need to treat consteval-only things as non-constant when
> mce_unknown?
You mean say early in cxx_eval_constant_expression do something like
if (flag_reflection
&& ctx->manifestly_const_eval == mce_unknown
&& is_consteval_only (t))
{
// some error if not quiet
*non_constant_p = true;
return t;
}
? Wouldn't that miss say
constexpr auto a = ^^::;
int b = false ? &a == &a : 42;
?
In any case, can that be postponed to incremental fixes?
And, in any case, I think we badly need (but probably partly depending on
CWG3150 resolution) some consteval-only caching, so that we don't walk
everything over and over again. Perhaps a tristate or 4-state,
consteval-only-{unknown,yes,no,error}, dunno if only on class types or
also on pointer/reference/array/function/method types too.
no would be for something that definitely is not a consteval-only and
doesn't directly or indirectly refer to incomplete types,
yes for something known to be consteval-only (directly or indirectly),
unknown for something that needs to be walked and if
https://gist.github.com/katzdm/a7dcf0222a870e7e7e967e3cbe06e1f2#file-cwg3150-md
is in that form, then error for the case where something
consteval-only-unknown is used in a context which requires it not to be
consteval-only.
Anyway, this has so far been tested on reflect/*
2026-02-11 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.
* 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 13:45:00.317623805 +0100
@@ -8092,6 +8092,77 @@ 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
a manifestly constant-evaluated context. E.g.:
@@ -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