https://gcc.gnu.org/g:d1c6023698dbf575af4affa05bb1dc9e90502a33
commit r16-7718-gd1c6023698dbf575af4affa05bb1dc9e90502a33 Author: Marek Polacek <[email protected]> Date: Mon Feb 23 14:16:13 2026 -0500 c++/reflection: fix wrong "consteval-only" error [PR123662] This patch fixes a very annoying problem where we emit a bogus check_out_of_consteval_use error. The error is provoked while processing [: std::meta::reflect_constant_array (data) :] The argument of a [: :] is a constant-expression = a manifestly constant-evaluated context, so any consteval-only exprs in it are OK. But in eval_reflect_constant_array we do get_template_parm_object which does push_to_top_level -- so we have no scope_chain, therefore any in_consteval_if_p and current_function_decl are cleared, so we're not in an immediate context. As part of this get_template_parm_object, we call cp_finish_decl -> check_initializer -> build_aggr_init -> -> build_vec_init. Here in build_vec_init try_const is true, but we still generate code for the initializer like <<< Unknown tree: expr_stmt (void) ++D.67757 >>>; <<< Unknown tree: expr_stmt (void) --D.67758 >>>; etc. We add ++D.67757 with finish_expr_stmt which calls convert_to_void -> check_out_of_consteval_use which causes the error because ++D.67757's type is consteval-only. Note that what we end up using is the simple _ZTAX... = {{.name=<<< Unknown tree: reflect_expr _ZTAXtlA2_KcLS_95EEE >>>, .none=1}} because we didn't see anything non-const in the initializer. Rather than convincing check_out_of_consteval_use that we are in an immediate context, we should only call check_out_of_consteval_use on the outermost statement, not sub-statements like the ++D.67757 above. This is not a complete fix though, see the FIXME. PR c++/123662 PR c++/123611 gcc/cp/ChangeLog: * cvt.cc (convert_to_void): Only call check_out_of_consteval_use when stmts_are_full_exprs_p. gcc/testsuite/ChangeLog: * g++.dg/reflect/reflect_constant_array5.C: New test. * g++.dg/reflect/reflect_constant_array6.C: New test. Reviewed-by: Jason Merrill <[email protected]> Diff: --- gcc/cp/cvt.cc | 6 +- .../g++.dg/reflect/reflect_constant_array5.C | 16 ++++ .../g++.dg/reflect/reflect_constant_array6.C | 104 +++++++++++++++++++++ 3 files changed, 124 insertions(+), 2 deletions(-) diff --git a/gcc/cp/cvt.cc b/gcc/cp/cvt.cc index 4042938da5e9..b1176317d996 100644 --- a/gcc/cp/cvt.cc +++ b/gcc/cp/cvt.cc @@ -1215,8 +1215,10 @@ convert_to_void (tree expr, impl_conv_void implicit, tsubst_flags_t complain) /* Detect using expressions of consteval-only types outside manifestly constant-evaluated contexts. We are going to discard this expression, - so we can't wait till cp_fold_immediate_r. */ - if (check_out_of_consteval_use (expr)) + so we can't wait till cp_fold_immediate_r. FIXME This is too early; + code like "int i = (^^i, 42);" is OK. We should stop discarding + expressions here (PR124249). */ + if (stmts_are_full_exprs_p () && check_out_of_consteval_use (expr)) return error_mark_node; if (VOID_TYPE_P (TREE_TYPE (expr))) diff --git a/gcc/testsuite/g++.dg/reflect/reflect_constant_array5.C b/gcc/testsuite/g++.dg/reflect/reflect_constant_array5.C new file mode 100644 index 000000000000..9e52f408d5a0 --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/reflect_constant_array5.C @@ -0,0 +1,16 @@ +// PR c++/123662 +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } + +#include <meta> + +struct arg { + std::meta::info name; + bool none = true; +}; + +template<size_t...Is> +void test1() { + constexpr auto py_arg_data = std::array{arg{std::meta::reflect_constant_string("_")}}; + constexpr auto short_py_arg_data = [: std::meta::reflect_constant_array(py_arg_data) :]; +} diff --git a/gcc/testsuite/g++.dg/reflect/reflect_constant_array6.C b/gcc/testsuite/g++.dg/reflect/reflect_constant_array6.C new file mode 100644 index 000000000000..918b07ffb881 --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/reflect_constant_array6.C @@ -0,0 +1,104 @@ +// PR c++/123611 +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } + +#include <string> +#include <meta> +#include <algorithm> +#include <ranges> +#include <sstream> + +namespace clap { + struct Flags { + bool use_short; + bool use_long; + }; + + template <typename T, Flags flags> + struct Option { + std::optional<T> initializer; + + Option() = default; + Option(T t) : initializer(t) { } + + static constexpr bool use_short = flags.use_short; + static constexpr bool use_long = flags.use_long; + }; + + consteval auto spec_to_opts(std::meta::info opts, std::meta::info spec) -> std::meta::info { + std::vector<std::meta::info> new_members; + for (auto member : + nonstatic_data_members_of(spec, std::meta::access_context::current())) { + auto new_type = template_arguments_of(type_of(member))[0]; + new_members.push_back(data_member_spec(new_type, {.name=identifier_of(member)})); + } + return define_aggregate(opts, new_members); + } + + struct Clap { + template <typename Spec> + auto parse(this Spec const& spec, int argc, const char** argv) { + std::vector<std::string_view> cmdline(argv + 1, argv + argc); + + struct Opts; + consteval { + spec_to_opts(^^Opts, ^^Spec); + } + Opts opts; + + constexpr auto ctx = std::meta::access_context::current(); + template for (constexpr auto Pair : + std::define_static_array( + std::views::zip(nonstatic_data_members_of(^^Spec, ctx), + nonstatic_data_members_of(^^Opts, ctx)) | + std::views::transform([](auto z) { return std::pair(get<0>(z), get<1>(z)); }))) { + constexpr auto sm = Pair.first; + constexpr auto om = Pair.second; + + auto& cur = spec.[:sm:]; + constexpr auto type = type_of(om); + + auto it = std::find_if(cmdline.begin(), cmdline.end(), + [&](std::string_view arg) { + return (cur.use_short && arg.size() == 2 && arg[0] == '-' && + arg[1] == identifier_of(sm)[0]) + || (cur.use_long && arg.starts_with("--") && + arg.substr(2) == identifier_of(sm)); + }); + + if (it == cmdline.end()) { + if constexpr (has_template_arguments(type) && + template_of(type) == ^^std::optional) { + continue; + } else if (cur.initializer) { + opts.[:om:] = *cur.initializer; + continue; + } else { + std::exit(EXIT_FAILURE); + } + } else if (it + 1 == cmdline.end()) { + std::exit(EXIT_FAILURE); + } + + std::stringstream iss; + iss << it[1]; + if (iss >> opts.[:om:]; !iss) { + std::exit(EXIT_FAILURE); + } + } + + return opts; + } + }; +} + +using namespace clap; +struct Args : Clap { + Option<std::string, Flags{.use_short=true, .use_long=true}> name; + Option<int, Flags{.use_short=true, .use_long=true}> count = 1; +}; + +int main(int argc, const char** argv) { + auto opts = Args{}.parse(argc, argv); + for (int i = 0; i < opts.count; ++i) { } +}
