See <https://wg21.link/P1306R5>.
.
Created attachment 61786 [details] gcc16-pr120776-wip.patch Very early WIP, this can just parse stuff, on say struct S { int a, b, c; }; struct T { long long a, b, c; }; constexpr S d = { 1, 2, 3 }, e = { 4, 5, 6 }, f = { 7, 8, 9 }; constexpr T j = { 10LL, 11LL, 12LL }; void foo () { template for (auto &[g, h, i] : { d, e, f, j }) // __builtin_printf ("%d %d %d\n", g, h, i); ; } it can parse it (and throws it away right now). With the printf uncommented (where the right behavior is then to warn about %d vs. %lld 3 times for the 4th iteration) it errors because decl_dependent_p doesn't have special case for expansion-statement for-range-declaration vars/structured bindings. Guess even before parsing the body I'll need to figure out which of the cases it is and depending on that whether the vars/sbs are dependent or not.
Created attachment 61817 [details] gcc16-pr120776-wip.patch Some further progress. Current main problem is instantiation of expansion stmt body. And really nothing is done so far about destructuring expansion stmts, break/continue isn't handled etc.
Created attachment 61819 [details] gcc16-pr120776-wip.patch This version can do destructuring expansion stmts as well (but for none of the kinds if for-range-declaration is a structured binding).
Created attachment 61842 [details] gcc16-pr120776-wip.patch To my big surprise, the instantiation seems to work even for variables defined outside of the construct, will need to figure out why. Anyway, this is the first version that can handle some examples of expanion stmts at runtime (including enumerating, iterating and destructuring); for now only when defined outside of templates.
Created attachment 61885 [details] gcc16-pr120776-wip.patch Updated WIP patch. As the expansion-stmt2.C testcase shows, if expansion-init is dependent and during first tsubst_stmt of it not dependent, those cases seem to work quite well (will need to work on diagnostics for control-flow-limited statement, testcase for no labels in the body etc. Anyway, still have a couple of major issues: 1) the instantiation when not inside of a template works weirdly; this is because tsubst_expr has: else if (local_variable_p (t) && ((r = retrieve_local_specialization (t)) || TREE_CODE (t) == PARM_DECL || uses_template_parms (DECL_CONTEXT (t)))) for non-DECL_LOCAL_DECL_P VAR_DECLs without DECL_TEMPLATE_INFO. The code right now registers local specialization of the for-range-declaration (and structured bindings in there if any), but nothing else. As in non-templates uses_template_parms (DECL_CONTEXT (t)) is false, it means only for-range-declaration is locally specialized and the rest is used directly (except PARM_DECLs). Now, we want PARM_DECLs and VAR_DECLs defined outside of the TEMPLATE_FOR_STMT to map to themselves, but VAR_DECLs defined inside of the TEMPLATE_FOR_STMT to be specialized. Shall I use some flag next to this || uses_template_parms (e.g. instead of 1-bit scope_chain->expansion_stmt meant primarily for the control-flow-limited diagnostics use 2 bits to differentiate between non-template instantiation during parsing vs. the rest? 2) from the paper, it is unclear when the lowering of expansion statement needs to happen; it says that for-range-declaration is not type dependent if expansion-init of iterating expansion stmt is not type dependent and I think I handle that already doing auto deduction in that case. But is there some strong reason why the expansion statement would need to be lowered still when processing_template_decl? Not really sure how to (partially) instantiate it in that case 3) the TEMPLATE_FOR_STMT part doesn't have the if (!finish_expansion_stmt (...)) case implemented, I guess I can handle it easily, but I'm afraid I don't understand how to construct a testcase, where something is only partially instantiated and the expansion-initializer is still type-dependent during that instantiation and it will need another instantiation later on
Regarding 1), I think the problem is that if there are automatic vars defined in the expansion stmt body, there is no DECL_EXPR created for them. The problem is start_decl doing: 6214 if (processing_template_decl) 6215 decl = push_template_decl (decl); There is ++processing_template_decl around parsing of the expansion stmt bodies, but push_template_decl then does: 5893 if (decl == error_mark_node || !current_template_parms) 5894 return error_mark_node; and because I haven't created current_template_parms if expansion stmt is not inside of a template, this returns error_mark_node and a lot of things break because of that obviously. So probably I need to arrange for current_template_parms to be set and restored back as well.
Created attachment 61896 [details] gcc16-pr120776-wip.patch I think I've 1) and 2) resolved and for 3) testcase which currently FAILs, so working on that next.
Created attachment 61911 [details] gcc16-pr120776-wip.patch Further progress.
Created attachment 61912 [details] gcc16-pr120776-wip.patch Latest state. Still need more testcases (though admittedly something will just not work until P2686R4 is implemented fully), figure out what to do about range extension (see https://github.com/cplusplus/CWG/issues/724 for some issues), whether using ptrdiff_t(i) that the patch currently uses for iterating expansion statements is ok (see https://github.com/cplusplus/CWG/issues/725 ) and at least add a testcase for redeclarations (both something declared in init-stmt with for-range-declaration and possibly (see https://github.com/cplusplus/CWG/issues/729 ) also in the body. I think the code currently does diagnose it even in the body, but will need testcases. Also tests for tuple based structured bindings (both in destructuring expansion statements and when structured bindings are used in for-range-declaration). Also tests for what happens when it is not iterating and is not valid destructuring either.
Created attachment 61958 [details] gcc16-pr120776.patch Full untested patch.
The master branch has been updated by Jakub Jelinek <jakub@gcc.gnu.org>: https://gcc.gnu.org/g:458773ac7bca792db044ad6c241f566c9ba87cb7 commit r16-3192-g458773ac7bca792db044ad6c241f566c9ba87cb7 Author: Jakub Jelinek <jakub@redhat.com> Date: Wed Aug 13 22:07:27 2025 +0200 c++: Implement C++26 P1306R5 - Expansion statements [PR120776] The following patch implements the C++26 P1306R5 - Expansion statements paper. When expansion statements are used outside of templates, the lowering of the statement uses push_tinst_level_loc and instantiates the body multiple times, otherwise when the new TEMPLATE_FOR_STMT statement is being instantiated and !processing_template_decl, it instantiates the body several times with just local_specialization_stack around each iteration but with the original args. Because the lowering of these statements is mostly about instantiation, I've put the lowering code into pt.cc rather than semantics.cc. Only destructuring expansion statements currently use in the patch temporary lifetime extension which matches the proposed resolution of https://cplusplus.github.io/CWG/issues/3043.html I'm not sure what will CWG decide about that if there will be some temporary lifetime extension for enumerating expansion statements and if yes, under what exact rules (e.g. whether it extends all the temporaries across one iteration of the body, or only if a reference is initialized or nothing at all). And for iterating expansion statements, I think I don't understand the P2686R4 rules well yet, I think if the expansion-initializer is used in static constexpr rvalue reference, then it isn't needed, but not sure if it won't be needed if static would be dropped (whether struct S { constexpr S () : s (0) {} constexpr ~S () {} int s; }; struct T { const S &t, &u; }; void foo () { constexpr T t = { S {}, S {} }; use (t.t, t.u); } is ok under P2686R4; though without constexpr before T I see S::~S () being called after use, not at the end of the t declaration, so maybe it is fine also without static). As per https://cplusplus.github.io/CWG/issues/3044.html the patch uses build_int_cst (ptrdiff_type_node, i) to create second operand of begin + i and doesn't lookup overloaded comma operator (note, I'm actually not even creating a lambda there, just using TARGET_EXPRs). I guess my preference would be dropping those 4 static keywords from [stmt.expand] but the patch does use those for now and it won't be possible to change that until the rest of P2686R4 is implemented. As per https://cplusplus.github.io/CWG/issues/3045.html it treats sk_template_for like sk_for for the purpose of redeclaration of vars in the body but doesn't yet reject [[fallthrough]]; in the expansion stmt body (when not nested in another switch). I'm not sure if cp_perform_range_for_lookup used in the patch is exactly what we want for the https://eel.is/c++draft/stmt.expand#3.2 - it does finish_call_expr on the perform_koenig_lookup as well, shouldn't for the decision whether it is iterating or destructing (i.e. tf_none) just call perform_koenig_lookup and check if it found some FUNCTION_DECL/OVERLOAD/TEMPLATE_DECL? cp_decomp_size in the patch has tsubst_flags_t argument and attempts to be SFINAE friendly, even when it isn't needed strictly for this patch. This is with PR96185 __builtin_structured_binding_size implementation in mind (to follow clang). The new TEMPLATE_FOR_STMT statement is expected to be lowered to something that doesn't use the statement at all, I've implemented break/continue discovery in the body, so all I needed was to punt on TEMPLATE_FOR_STMT in potential_constant_expression_1 so that we don't try to constant evaluate it when it is still dependent (and cxx_eval_constant_expression rejects it without any extra code). I think only enumerating and iterating expansion statements can have zero iteration, because for destructuring ones it doesn't use a structured binding pack and so valid structured binding has to have at least one iteration. Though https://cplusplus.github.io/CWG/issues/3048.html could change that, this patch currently rejects it though. 2025-08-13 Jakub Jelinek <jakub@redhat.com> PR c++/120776 gcc/c-family/ * c-cppbuiltin.cc (c_cpp_builtins): Predefine __cpp_expansion_statements=202506L for C++26. gcc/cp/ * cp-tree.def: Implement C++26 P1306R5 - Expansion statements. (TEMPLATE_FOR_STMT): New tree code. * cp-tree.h (struct saved_scope): Add expansion_stmt. (in_expansion_stmt): Define. (TEMPLATE_FOR_DECL, TEMPLATE_FOR_EXPR, TEMPLATE_FOR_BODY, TEMPLATE_FOR_SCOPE, TEMPLATE_FOR_INIT_STMT): Define. (struct tinst_level): Adjust comment. (cp_decomp_size, finish_expansion_stmt, do_pushlevel, cp_build_range_for_decls, build_range_temp, cp_perform_range_for_lookup, begin_template_for_scope): Declare. (finish_range_for_stmt): Remove declaration. * cp-objcp-common.cc (cp_common_init_ts): Handle TEMPLATE_FOR_STMT. * name-lookup.h (enum scope_kind): Add sk_template_for enumerator. (struct cp_binding_level): Enlarge kind bitfield from 4 to 5 bits. Adjust comment with remaining space bits. * name-lookup.cc (check_local_shadow): Handle sk_template_for like sk_for. (cp_binding_level_descriptor): Add entry for sk_template_for. (begin_scope): Handle sk_template_for. * parser.h (IN_EXPANSION_STMT): Define. * parser.cc (cp_debug_parser): Print IN_EXPANSION_STMT bit. (cp_parser_lambda_expression): Temporarily clear in_expansion_stmt. (cp_parser_statement): Handle RID_TEMPLATE followed by RID_FOR for C++11. (cp_parser_label_for_labeled_statement): Complain about named labels inside of expansion stmt body. (cp_hide_range_decl): New function. (cp_parser_range_for): Use it. Adjust do_range_for_auto_deduction caller. Remove second template argument from auto_vecs bindings and names. (build_range_temp): No longer static. (do_range_for_auto_deduction): Add expansion_stmt argument. (cp_build_range_for_decls): New function. (cp_convert_range_for): Use it. Call cp_perform_range_for_lookup rather than cp_parser_perform_range_for_lookup. (cp_parser_perform_range_for_lookup): Rename to ... (cp_perform_range_for_lookup): ... this. No longer static. Add complain argument and handle it. (cp_parser_range_for_member_function): Rename to ... (cp_range_for_member_function): ... this. (cp_parser_expansion_statement): New function. (cp_parser_jump_statement): Handle IN_EXPANSION_STMT. (cp_convert_omp_range_for): Adjust do_range_for_auto_deduction caller. Call cp_perform_range_for_lookup rather than cp_parser_perform_range_for_lookup. * error.cc (print_instantiation_full_context): Handle tldcl being TEMPLATE_FOR_STMT. (print_instantiation_partial_context_line): Likewise. * constexpr.cc (potential_constant_expression_1): Handle TEMPLATE_FOR_STMT. * decl.cc (poplevel_named_label_1): Use obl instead of bl->level_chain. (finish_case_label): Diagnose case labels inside of template for. (find_decomp_class_base): Add complain argument, don't diagnose anything and just return error_mark_node if tf_none, adjust recursive call. (cp_decomp_size): New function. (cp_finish_decomp): Adjust find_decomp_class_base caller. * semantics.cc (do_pushlevel): No longer static. (begin_template_for_scope): New function. * pt.cc (push_tinst_level_loc): Handle TEMPLATE_FOR_STMT. (reopen_tinst_level): Likewise. (tsubst_stmt): Handle TEMPLATE_FOR_STMT. (struct expansion_stmt_bc): New type. (expansion_stmt_find_bc_r, finish_expansion_stmt): New functions. * decl2.cc (decl_dependent_p): Return true for current function's decl if in_expansion_stmt. * call.cc (extend_ref_init_temps): Don't extend_all_temps if TREE_STATIC (decl). * cxx-pretty-print.cc (cxx_pretty_printer::statement): Handle TEMPLATE_FOR_STMT. gcc/testsuite/ * g++.dg/cpp1z/decomp64.C: New test. * g++.dg/cpp26/expansion-stmt1.C: New test. * g++.dg/cpp26/expansion-stmt2.C: New test. * g++.dg/cpp26/expansion-stmt3.C: New test. * g++.dg/cpp26/expansion-stmt4.C: New test. * g++.dg/cpp26/expansion-stmt5.C: New test. * g++.dg/cpp26/expansion-stmt6.C: New test. * g++.dg/cpp26/expansion-stmt7.C: New test. * g++.dg/cpp26/expansion-stmt8.C: New test. * g++.dg/cpp26/expansion-stmt9.C: New test. * g++.dg/cpp26/expansion-stmt10.C: New test. * g++.dg/cpp26/expansion-stmt11.C: New test. * g++.dg/cpp26/expansion-stmt12.C: New test. * g++.dg/cpp26/expansion-stmt13.C: New test. * g++.dg/cpp26/expansion-stmt14.C: New test. * g++.dg/cpp26/expansion-stmt15.C: New test. * g++.dg/cpp26/expansion-stmt16.C: New test. * g++.dg/cpp26/expansion-stmt17.C: New test. * g++.dg/cpp26/expansion-stmt18.C: New test. * g++.dg/cpp26/expansion-stmt19.C: New test. * g++.dg/cpp26/feat-cxx26.C: Add __cpp_expansion_statements tests.
Implemented for GCC 16.