https://gcc.gnu.org/g:b95955b8854ae1e859cc7c87700922924e9a5e5f
commit r16-7899-gb95955b8854ae1e859cc7c87700922924e9a5e5f Author: Marek Polacek <[email protected]> Date: Mon Mar 2 17:12:56 2026 -0500 c++: reusing typedefs in template for [PR124229] This is a crash on code like: template for (constexpr auto val : define_static_array (enumerators_of (^^E))) { constexpr auto a = annotations_of(val)[0]; using U = [:type_of(a):]; constexpr auto m1 = extract<U>(a); } because the template arg to extract wasn't substituted to "info". Once I dug deeper I realized this problem isn't tied to Reflection: we also crash here: template for (constexpr auto val : { 42 }) { using U = decltype(val); foo<U>(); } because we emit code for foo() that still has a DECLTYPE_TYPE in it. The problem is in tsubst and reusing typedefs. Normally, for code like template<typename T> void foo () { using U = T; U u; } we do the DECL_FUNCTION_SCOPE_P -> retrieve_local_specialization call. This call only happens in function templates (that are not explicit specializations), but the "template for" above are both in non-template functions. So we end up returning the original tree: /* The typedef is from a non-template context. */ return t; It seems clear that this is the wrong thing to do, and that the DECL_FUNCTION_SCOPE_P code should happen in this scenario as well. [temp.decls.general] tells me that "For the purpose of name lookup and instantiation, the compound-statement of an expansion-statement is considered a template definition." so I'm guessing that we want to check for an expansion-statement as well. As decl_dependent_p says, in_expansion_stmt is false when instantiating, so I'm looking for sk_template_for. PR c++/124229 gcc/cp/ChangeLog: * pt.cc (in_expansion_stmt_p): New. (tsubst): When reusing typedefs, do retrieve_local_specialization also when in_expansion_stmt_p is true. gcc/testsuite/ChangeLog: * g++.dg/cpp26/expansion-stmt32.C: New test. * g++.dg/reflect/expansion-stmt2.C: New test. Reviewed-by: Jason Merrill <[email protected]> Diff: --- gcc/cp/pt.cc | 26 +++++++++++++++-- gcc/testsuite/g++.dg/cpp26/expansion-stmt32.C | 14 +++++++++ gcc/testsuite/g++.dg/reflect/expansion-stmt2.C | 40 ++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc index 33a9ab3ef6da..ddf492b34354 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -16832,6 +16832,23 @@ tsubst_splice_expr (tree t, tree args, tsubst_flags_t complain, tree in_decl) return op; } +/* Return true iff we're in an expansion statement. */ + +static bool +in_expansion_stmt_p () +{ + if (in_expansion_stmt) + return true; + + /* In instantiations in_expansion_stmt is false. */ + for (cp_binding_level *b = current_binding_level; + b && b->kind != sk_function_parms; + b = b->level_chain) + if (b->kind == sk_template_for) + return true; + return false; +} + /* Take the tree structure T and replace template parameters used therein with the argument vector ARGS. IN_DECL is an associated decl for diagnostics. If an error occurs, returns ERROR_MARK_NODE. @@ -16912,9 +16929,12 @@ tsubst (tree t, tree args, tsubst_flags_t complain, tree in_decl) tree gen_args = tsubst (DECL_TI_ARGS (decl), args, complain, in_decl); r = retrieve_specialization (tmpl, gen_args, 0); } - else if (DECL_FUNCTION_SCOPE_P (decl) - && DECL_TEMPLATE_INFO (DECL_CONTEXT (decl)) - && uses_template_parms (DECL_TI_ARGS (DECL_CONTEXT (decl)))) + else if ((DECL_FUNCTION_SCOPE_P (decl) + && DECL_TEMPLATE_INFO (DECL_CONTEXT (decl)) + && uses_template_parms (DECL_TI_ARGS (DECL_CONTEXT (decl)))) + /* The { } of an expansion-statement is considered a template + definition. */ + || in_expansion_stmt_p ()) r = retrieve_local_specialization (decl); else /* The typedef is from a non-template context. */ diff --git a/gcc/testsuite/g++.dg/cpp26/expansion-stmt32.C b/gcc/testsuite/g++.dg/cpp26/expansion-stmt32.C new file mode 100644 index 000000000000..772834701c19 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/expansion-stmt32.C @@ -0,0 +1,14 @@ +// PR c++/124229 +// { dg-do compile { target c++26 } } + +template<typename T> +void foo () { } + +int main() +{ + template for (constexpr auto val : { 42 }) + { + using U = decltype(val); + foo<U>(); + } +} diff --git a/gcc/testsuite/g++.dg/reflect/expansion-stmt2.C b/gcc/testsuite/g++.dg/reflect/expansion-stmt2.C new file mode 100644 index 000000000000..ac3d75d804e2 --- /dev/null +++ b/gcc/testsuite/g++.dg/reflect/expansion-stmt2.C @@ -0,0 +1,40 @@ +// PR c++/124229 +// { dg-do compile { target c++26 } } +// { dg-additional-options "-freflection" } + +#include <meta> + +enum class E +{ + X [[=1]] +}; + +template<typename T> +void foo () +{ +} + +void +f () +{ + template for (constexpr auto val : define_static_array (enumerators_of (^^E))) + { + constexpr auto a = annotations_of(val)[0]; + using U = [:type_of(a):]; + constexpr auto m1 = extract<U>(a); + constexpr auto m2 = extract<typename [:type_of(a):]>(a); + } + + template for (constexpr auto val : define_static_array (enumerators_of (^^E))) + { + constexpr auto a = annotations_of(val)[0]; + using U = [:type_of(a):]; + foo<U>(); + } + + [](auto a) { + using V = [:type_of(^^a):];; + constexpr auto rt = std::meta::reflect_constant (1); + constexpr auto m1 = std::meta::extract<V>(rt); + }(1); +}
