Hi! On Thu, Feb 13, 2025 at 12:08:30AM +0100, Jason Merrill wrote:
Thanks for the review. > > + if (tsi_end_p (iter)) > > + *body_p = NULL_TREE; > > + else > > + *body_p = tsi_stmt (iter); > > This could use a comment that we're setting _BODY temporarily for the next > function. Done. > > + gcc_assert (TREE_CODE (BIND_EXPR_BODY (*prep_p)) == STATEMENT_LIST); > > + tree stmt_list = BIND_EXPR_BODY (*prep_p); > > It would be shorter to assert TREE_CODE (stmt_list)? Changed. > > + if (tsi_one_before_end_p (iter)) > > + *body_p = build_empty_stmt (input_location); > > + else > > + { > > + tsi_next (&iter); > > + *body_p = NULL_TREE; > > + while (!tsi_end_p (iter)) > > + { > > + tree t = tsi_stmt (iter); > > + tsi_delink (&iter); > > + append_to_statement_list_force (t, body_p); > > + } > > I wonder about factoring this if/else out into a split_statement_list (or > tsi_split_list?) function. Done (used tsi_split_stmt_list). > > - auto cleanup_cond = [=] { > > + auto cleanup_cond = [=, &cond_cleanup_depth] { > > Let's just change = to &. Done. > > + if (cond_cleanup) > > + { > > + /* If COND_CLEANUP is non-NULL, we need to evaluate DEPTH > > + nested STATEMENT_EXPRs from inside of BIND_EXPR_BODY, > > + but defer the evaluation of CLEANUP_EXPRs at the end > > + of those STATEMENT_EXPRs. */ > > Instead of (both) STATEMENT_EXPR do you mean CLEANUP_STMT? I meant STATEMENT_LIST instead of STATEMENT_EXPR, reworded slightly. > > + cond_cleanup_depth = 0; > > + tree s = BIND_EXPR_BODY (cond_prep); > > + for (unsigned depth = tree_to_uhwi (cond_cleanup); > > + depth; --depth) > > + { > > + for (tree_stmt_iterator i = tsi_start (s); > > + !tsi_end_p (i); ++i) > > + { > > + tree stmt = *i; > > + if (TREE_CODE (stmt) == DEBUG_BEGIN_STMT) > > + continue; > > + if (tsi_one_before_end_p (i)) > > + { > > + gcc_assert (TREE_CODE (stmt) == CLEANUP_STMT); > > + if (*jump_target) > > + { > > + depth = 1; > > + break; > > + } > > Why is this here, given the jump_target handling a bit below? This is IMHO needed the way it is written. This whole block is essentially trying to do what cxx_eval_constant_expression would do, except it skips evaluation of the CLEANUP_EXPRs of the last depth CLEANUP_STMTs (and a small optimization, we know there is no continue label in any of these STATEMENT_LISTs, so no need to special case those). cxx_eval_statement_list on the tsi_one_before_end_p statement would just call cxx_eval_constant_expression (ok, perhaps with vc_prvalue rather than vc_discard, but we know we aren't in a statement expression too). And that on CLEANUP_STMT will just do: if (jump_target && *jump_target) ... default: return NULL_TREE; - CLEANUP_STMT isn't one of those into which it recurses, one can't just switch etc. across a variable declaration. And in that case the CLEANUP_EXPR isn't evaluated either. So, the purpose of this is if the CLEANUP_STMT wouldn't be evaluated, stop iterating, so don't evaluate CLEANUP_BODY of it, and don't increase cond_cleanup_depth, so that its CLEANUP_EXPR won't be evaluated either. > > > + ++cond_cleanup_depth; > > + if (depth > 1) > > + { > > + s = CLEANUP_BODY (stmt); > > + break; > > + } > > + cxx_eval_constant_expression (ctx, > > + CLEANUP_BODY (stmt), > > + vc_discard, > > + non_constant_p, > > + overflow_p, > > + jump_target); > > + break; > > + } > > Are you assuming that stmt will not be a CLEANUP_STMT here? Maybe swap the This assumes stmt is CLEANUP_STMT as verified by the gcc_assert. > one_before_end and CLEANUP_STMT tests between the if and assert above? I guess CLEANUP_STMTs wouldn't appear in the middle of STATEMENT_LIST, but if they would (say some cleanup no longer applying after certain point), the check for last stmt is IMHO the right one, that is what we've verified is an CLEANUP_STMT during construction and it is what the cleanup also searches for to find out what CLEANUP_EXPR should be evaluated. Here is an updated patch, passed GXX_TESTSUITE_STDS=98,11,14,17,20,23,26 make check-g++ RUNTESTFLAGS="debug.exp=cleanup1.C dg.exp='name-independent-decl*.C redeclaration-*.C pr18770.C for*.C stmtexpr* constexpr-86769.C cpp26/decomp*' old-deja.exp=cond.C" and will bootstrap/regtest it momentarily. 2025-02-13 Jakub Jelinek <ja...@redhat.com> PR c++/118822 PR c++/118833 gcc/ * tree-iterator.h (tsi_split_stmt_list): Declare. * tree-iterator.cc (tsi_split_stmt_list): New function. gcc/c-family/ * c-common.h (WHILE_COND_CLEANUP): Change description in comment. (FOR_COND_CLEANUP): Likewise. * c-gimplify.cc (genericize_c_loop): Adjust for COND_CLEANUP being CLEANUP_STMT/TRY_FINALLY_EXPR trailing nesting depth instead of actual cleanup. gcc/cp/ * semantics.cc (adjust_loop_decl_cond): Allow multiple trailing CLEANUP_STMT levels in *BODY_P. Set *CLEANUP_P to the number of levels rather than one particular cleanup, keep the cleanups in *PREP_P. Set *BODY_P to the last stmt in the cur_stmt_list or NULL if *CLEANUP_P and the innermost cur_stmt_list is empty. (finish_loop_cond_prep): New function. (finish_while_stmt, finish_for_stmt): Use it. Don't call set_one_cleanup_loc. * constexpr.cc (cxx_eval_loop_expr): Adjust handling of {FOR,WHILE}_COND_{PREP,CLEANUP}. gcc/testsuite/ * g++.dg/expr/for9.C: New test. * g++.dg/cpp26/decomp12.C: New test. --- gcc/tree-iterator.h.jj 2025-01-02 11:23:12.689570290 +0100 +++ gcc/tree-iterator.h 2025-02-13 00:27:58.345105986 +0100 @@ -138,6 +138,7 @@ extern void tsi_link_after (tree_stmt_it enum tsi_iterator_update); extern void tsi_delink (tree_stmt_iterator *); +extern tree tsi_split_stmt_list (location_t, tree_stmt_iterator); extern tree alloc_stmt_list (void); extern void free_stmt_list (tree); --- gcc/tree-iterator.cc.jj 2025-01-02 11:23:41.231171833 +0100 +++ gcc/tree-iterator.cc 2025-02-13 00:45:03.367640579 +0100 @@ -284,6 +284,28 @@ tsi_delink (tree_stmt_iterator *i) i->ptr = next; } +/* Split a STATEMENT_LIST in I.contrainer into two, all statements + from the start until I.ptr inclusive will remain in the original + one, all statements after I.ptr are removed from that STATEMENT_LIST + and returned as a new STATEMENT_LIST. If I is the last statement, + an empty statement with LOC location is returned. */ + +tree +tsi_split_stmt_list (location_t loc, tree_stmt_iterator i) +{ + if (tsi_one_before_end_p (i)) + return build_empty_stmt (loc); + tsi_next (&i); + tree ret = NULL_TREE; + while (!tsi_end_p (i)) + { + tree t = tsi_stmt (i); + tsi_delink (&i); + append_to_statement_list_force (t, &ret); + } + return ret; +} + /* Return the first expression in a sequence of COMPOUND_EXPRs, or in a STATEMENT_LIST, disregarding DEBUG_BEGIN_STMTs, recursing into a STATEMENT_LIST if that's the first non-DEBUG_BEGIN_STMT. */ --- gcc/c-family/c-common.h.jj 2025-02-12 11:03:53.467558140 +0100 +++ gcc/c-family/c-common.h 2025-02-13 00:20:07.931404300 +0100 @@ -1518,7 +1518,8 @@ extern tree build_userdef_literal (tree /* WHILE_STMT accessors. These give access to the condition of the while statement, the body, and name of the while statement, and - condition preparation statements and its cleanup, respectively. */ + condition preparation statements and number of its nested cleanups, + respectively. */ #define WHILE_COND(NODE) TREE_OPERAND (WHILE_STMT_CHECK (NODE), 0) #define WHILE_BODY(NODE) TREE_OPERAND (WHILE_STMT_CHECK (NODE), 1) #define WHILE_NAME(NODE) TREE_OPERAND (WHILE_STMT_CHECK (NODE), 2) @@ -1533,7 +1534,8 @@ extern tree build_userdef_literal (tree /* FOR_STMT accessors. These give access to the init statement, condition, update expression, body and name of the for statement, - and condition preparation statements and its cleanup, respectively. */ + and condition preparation statements and number of its nested cleanups, + respectively. */ #define FOR_INIT_STMT(NODE) TREE_OPERAND (FOR_STMT_CHECK (NODE), 0) #define FOR_COND(NODE) TREE_OPERAND (FOR_STMT_CHECK (NODE), 1) #define FOR_EXPR(NODE) TREE_OPERAND (FOR_STMT_CHECK (NODE), 2) --- gcc/c-family/c-gimplify.cc.jj 2025-02-12 11:03:53.479557973 +0100 +++ gcc/c-family/c-gimplify.cc 2025-02-13 00:20:07.955403978 +0100 @@ -258,8 +258,10 @@ expr_loc_or_loc (const_tree expr, locati for C++ for/while loops with variable declaration as condition. COND_PREP is a BIND_EXPR with the declaration and initialization of the condition variable, into which COND, BODY, continue label if needed and INCR if - non-NULL should be appended, and COND_CLEANUP are statements which should - be evaluated after that or if anything in COND, BODY or INCR throws. */ + non-NULL should be appended, and COND_CLEANUP is number of nested + CLEANUP_STMT -> TRY_FINALLY_EXPR statements at the end. If non-NULL, + COND, BODY, continue label if needed and INCR if non-NULL should be + appended to the body of the COND_CLEANUP's nested TRY_FINALLY_EXPR. */ static void genericize_c_loop (tree *stmt_p, location_t start_locus, tree cond, tree body, @@ -278,7 +280,6 @@ genericize_c_loop (tree *stmt_p, locatio walk_tree_1 (&cond_prep, func, data, NULL, lh); walk_tree_1 (&cond, func, data, NULL, lh); walk_tree_1 (&incr, func, data, NULL, lh); - walk_tree_1 (&cond_cleanup, func, data, NULL, lh); blab = begin_bc_block (bc_break, start_locus); clab = begin_bc_block (bc_continue, start_locus); @@ -309,36 +310,24 @@ genericize_c_loop (tree *stmt_p, locatio EXPR; goto top; - or - - try { - if (COND); else break; - BODY; - cont: - EXPR; - } finally { - COND_CLEANUP - } - - appended into COND_PREP body. */ + appended into COND_PREP body or body of some TRY_FINALLY_EXPRs + at the end of COND_PREP. */ gcc_assert (cond_is_first && TREE_CODE (cond_prep) == BIND_EXPR); tree top = build1 (LABEL_EXPR, void_type_node, create_artificial_label (start_locus)); exit = build1 (GOTO_EXPR, void_type_node, LABEL_EXPR_LABEL (top)); append_to_statement_list (top, &outer_stmt_list); append_to_statement_list (cond_prep, &outer_stmt_list); - stmt_list = BIND_EXPR_BODY (cond_prep); - BIND_EXPR_BODY (cond_prep) = NULL_TREE; stmt_list_p = &BIND_EXPR_BODY (cond_prep); - if (cond_cleanup && TREE_SIDE_EFFECTS (cond_cleanup)) - { - t = build2_loc (EXPR_LOCATION (cond_cleanup), TRY_FINALLY_EXPR, - void_type_node, NULL_TREE, cond_cleanup); - append_to_statement_list (t, &stmt_list); - *stmt_list_p = stmt_list; - stmt_list_p = &TREE_OPERAND (t, 0); - stmt_list = NULL_TREE; - } + if (cond_cleanup) + for (unsigned depth = tree_to_uhwi (cond_cleanup); depth; --depth) + { + t = tsi_stmt (tsi_last (*stmt_list_p)); + gcc_assert (TREE_CODE (t) == TRY_FINALLY_EXPR); + stmt_list_p = &TREE_OPERAND (t, 0); + } + stmt_list = *stmt_list_p; + *stmt_list_p = NULL_TREE; tree after_cond = create_artificial_label (cond_locus); tree goto_after_cond = build1 (GOTO_EXPR, void_type_node, after_cond); t = build1 (GOTO_EXPR, void_type_node, get_bc_label (bc_break)); --- gcc/cp/semantics.cc.jj 2025-02-12 11:04:10.211324554 +0100 +++ gcc/cp/semantics.cc 2025-02-13 00:45:37.501193676 +0100 @@ -790,8 +790,8 @@ finish_cond (tree *cond_p, tree expr) while (A x = 42) { } for (; A x = 42;) { } move the *BODY_P statements as a BIND_EXPR into {FOR,WHILE}_COND_PREP - and if there is any CLEANUP_STMT at the end, remove that and - put the cleanup into {FOR,WHILE}_COND_CLEANUP. + and if there are any CLEANUP_STMT at the end, remember their count in + {FOR,WHILE}_COND_CLEANUP. genericize_c_loop will then handle it appropriately. In particular, the {FOR,WHILE}_COND, {FOR,WHILE}_BODY, if used continue label and FOR_EXPR will be appended into the {FOR,WHILE}_COND_PREP BIND_EXPR, @@ -807,26 +807,88 @@ adjust_loop_decl_cond (tree *body_p, tre return; gcc_assert (!processing_template_decl); - if (*body_p != cur_stmt_list) + *prep_p = *body_p; + if (*prep_p != cur_stmt_list) { - /* There can be either no cleanup at all, if the condition - declaration doesn't have non-trivial destructor, or a single - one if it does. In that case extract it into *CLEANUP_P. */ - gcc_assert (stmt_list_stack->length () > 1 - && (*stmt_list_stack)[stmt_list_stack->length () - - 2] == *body_p); - tree_stmt_iterator last = tsi_last (*body_p); - gcc_assert (tsi_one_before_end_p (last) - && TREE_CODE (tsi_stmt (last)) == CLEANUP_STMT - && CLEANUP_BODY (tsi_stmt (last)) == cur_stmt_list - && tsi_end_p (tsi_last (cur_stmt_list)) - && !CLEANUP_EH_ONLY (tsi_stmt (last))); - *cleanup_p = CLEANUP_EXPR (tsi_stmt (last)); - tsi_delink (&last); + /* There can be just one CLEANUP_STMT, or there could be multiple + nested CLEANUP_STMTs, e.g. for structured bindings used as + condition. */ + gcc_assert (stmt_list_stack->length () > 1); + for (unsigned i = stmt_list_stack->length () - 2; ; --i) + { + tree t = (*stmt_list_stack)[i]; + tree_stmt_iterator last = tsi_last (t); + gcc_assert (tsi_one_before_end_p (last) + && TREE_CODE (tsi_stmt (last)) == CLEANUP_STMT + && (CLEANUP_BODY (tsi_stmt (last)) + == (*stmt_list_stack)[i + 1]) + && !CLEANUP_EH_ONLY (tsi_stmt (last))); + if (t == *prep_p) + { + *cleanup_p = build_int_cst (long_unsigned_type_node, + stmt_list_stack->length () - 1 - i); + break; + } + gcc_assert (i >= 1); + } } current_binding_level->keep = true; - *prep_p = *body_p; - *body_p = push_stmt_list (); + tree_stmt_iterator iter = tsi_last (cur_stmt_list); + /* Temporarily store in {FOR,WHILE}_BODY the last statement of + the innnermost statement list or NULL if it has no statement. + This is used in finish_loop_cond_prep to find out the splitting + point and then {FOR,WHILE}_BODY will be changed to the actual + body. */ + if (tsi_end_p (iter)) + *body_p = NULL_TREE; + else + *body_p = tsi_stmt (iter); +} + +/* Finalize {FOR,WHILE}_{BODY,COND_PREP} after the loop body. + The above function initialized *BODY_P to the last statement + in *PREP_P at that point. + Call do_poplevel on *PREP_P and move everything after that + former last statement into *BODY_P. genericize_c_loop + will later put those parts back together. + CLEANUP is {FOR,WHILE}_COND_CLEANUP. */ + +static void +finish_loop_cond_prep (tree *body_p, tree *prep_p, tree cleanup) +{ + *prep_p = do_poplevel (*prep_p); + gcc_assert (TREE_CODE (*prep_p) == BIND_EXPR); + if (BIND_EXPR_BODY (*prep_p) == *body_p) + { + gcc_assert (cleanup == NULL_TREE); + *body_p = build_empty_stmt (input_location); + return; + } + tree stmt_list = BIND_EXPR_BODY (*prep_p); + gcc_assert (TREE_CODE (stmt_list) == STATEMENT_LIST); + if (cleanup) + { + tree_stmt_iterator iter = tsi_last (stmt_list); + gcc_assert (TREE_CODE (tsi_stmt (iter)) == CLEANUP_STMT); + for (unsigned depth = tree_to_uhwi (cleanup); depth > 1; --depth) + { + gcc_assert (TREE_CODE (CLEANUP_BODY (tsi_stmt (iter))) + == STATEMENT_LIST); + iter = tsi_last (CLEANUP_BODY (tsi_stmt (iter))); + gcc_assert (TREE_CODE (tsi_stmt (iter)) == CLEANUP_STMT); + } + if (*body_p == NULL_TREE) + { + *body_p = CLEANUP_BODY (tsi_stmt (iter)); + CLEANUP_BODY (tsi_stmt (iter)) = build_empty_stmt (input_location); + return; + } + stmt_list = CLEANUP_BODY (tsi_stmt (iter)); + } + tree_stmt_iterator iter = tsi_start (stmt_list); + while (tsi_stmt (iter) != *body_p) + tsi_next (&iter); + *body_p = tsi_split_stmt_list (input_location, iter); } /* Finish a goto-statement. */ @@ -1437,14 +1499,13 @@ void finish_while_stmt (tree while_stmt) { end_maybe_infinite_loop (boolean_true_node); - WHILE_BODY (while_stmt) - = (WHILE_COND_PREP (while_stmt) - ? pop_stmt_list (WHILE_BODY (while_stmt)) - : do_poplevel (WHILE_BODY (while_stmt))); - finish_loop_cond (&WHILE_COND (while_stmt), WHILE_BODY (while_stmt)); if (WHILE_COND_PREP (while_stmt)) - WHILE_COND_PREP (while_stmt) = do_poplevel (WHILE_COND_PREP (while_stmt)); - set_one_cleanup_loc (WHILE_COND_CLEANUP (while_stmt), input_location); + finish_loop_cond_prep (&WHILE_BODY (while_stmt), + &WHILE_COND_PREP (while_stmt), + WHILE_COND_CLEANUP (while_stmt)); + else + WHILE_BODY (while_stmt) = do_poplevel (WHILE_BODY (while_stmt)); + finish_loop_cond (&WHILE_COND (while_stmt), WHILE_BODY (while_stmt)); } /* Begin a do-statement. Returns a newly created DO_STMT if @@ -1709,17 +1770,16 @@ finish_for_stmt (tree for_stmt) RANGE_FOR_BODY (for_stmt) = do_poplevel (RANGE_FOR_BODY (for_stmt)); else { - FOR_BODY (for_stmt) - = (FOR_COND_PREP (for_stmt) - ? pop_stmt_list (FOR_BODY (for_stmt)) - : do_poplevel (FOR_BODY (for_stmt))); + if (FOR_COND_PREP (for_stmt)) + finish_loop_cond_prep (&FOR_BODY (for_stmt), + &FOR_COND_PREP (for_stmt), + FOR_COND_CLEANUP (for_stmt)); + else + FOR_BODY (for_stmt) = do_poplevel (FOR_BODY (for_stmt)); if (FOR_COND (for_stmt)) finish_loop_cond (&FOR_COND (for_stmt), FOR_EXPR (for_stmt) ? integer_one_node : FOR_BODY (for_stmt)); - if (FOR_COND_PREP (for_stmt)) - FOR_COND_PREP (for_stmt) = do_poplevel (FOR_COND_PREP (for_stmt)); - set_one_cleanup_loc (FOR_COND_CLEANUP (for_stmt), input_location); } /* Pop the scope for the body of the loop. */ --- gcc/cp/constexpr.cc.jj 2025-02-12 11:03:53.504557624 +0100 +++ gcc/cp/constexpr.cc 2025-02-13 00:42:01.851024001 +0100 @@ -7153,6 +7153,7 @@ cxx_eval_loop_expr (const constexpr_ctx tree body, cond = NULL_TREE, expr = NULL_TREE; tree cond_prep = NULL_TREE, cond_cleanup = NULL_TREE; + unsigned cond_cleanup_depth = 0; int count = 0; switch (TREE_CODE (t)) { @@ -7188,11 +7189,25 @@ cxx_eval_loop_expr (const constexpr_ctx } if (cond_prep) gcc_assert (TREE_CODE (cond_prep) == BIND_EXPR); - auto cleanup_cond = [=] { + auto cleanup_cond = [&] { /* Clean up the condition variable after each iteration. */ - if (cond_cleanup && !*non_constant_p) - cxx_eval_constant_expression (ctx, cond_cleanup, vc_discard, - non_constant_p, overflow_p); + if (cond_cleanup_depth && !*non_constant_p) + { + auto_vec<tree, 4> cleanups (cond_cleanup_depth); + tree s = BIND_EXPR_BODY (cond_prep); + unsigned i; + for (i = cond_cleanup_depth; i; --i) + { + tree_stmt_iterator iter = tsi_last (s); + s = tsi_stmt (iter); + cleanups.quick_push (CLEANUP_EXPR (s)); + s = CLEANUP_BODY (s); + } + tree c; + FOR_EACH_VEC_ELT_REVERSE (cleanups, i, c) + cxx_eval_constant_expression (ctx, c, vc_discard, non_constant_p, + overflow_p); + } if (cond_prep) for (tree decl = BIND_EXPR_VARS (cond_prep); decl; decl = DECL_CHAIN (decl)) @@ -7227,9 +7242,77 @@ cxx_eval_loop_expr (const constexpr_ctx for (tree decl = BIND_EXPR_VARS (cond_prep); decl; decl = DECL_CHAIN (decl)) ctx->global->clear_value (decl); - cxx_eval_constant_expression (ctx, BIND_EXPR_BODY (cond_prep), - vc_discard, non_constant_p, - overflow_p, jump_target); + if (cond_cleanup) + { + /* If COND_CLEANUP is non-NULL, we need to evaluate DEPTH + nested STATEMENT_LISTs from inside of BIND_EXPR_BODY, + but defer the evaluation of CLEANUP_EXPRs of CLEANUP_STMT + at the end of those STATEMENT_LISTs. */ + cond_cleanup_depth = 0; + tree s = BIND_EXPR_BODY (cond_prep); + for (unsigned depth = tree_to_uhwi (cond_cleanup); + depth; --depth) + { + for (tree_stmt_iterator i = tsi_start (s); + !tsi_end_p (i); ++i) + { + tree stmt = *i; + if (TREE_CODE (stmt) == DEBUG_BEGIN_STMT) + continue; + if (tsi_one_before_end_p (i)) + { + /* The last statement in the STATEMENT_LIST + has to be a CLEANUP_STMT (verified in + finish_loop_cond_prep). We want to + evaluate just its CLEANUP_BODY part but not + CLEANUP_EXPR part just yet. */ + gcc_assert (TREE_CODE (stmt) == CLEANUP_STMT); + /* If the CLEANUP_STMT is not actually to be + evaluated, don't increment cond_cleanup_depth + so that we don't evaluate the CLEANUP_EXPR + for it later either. */ + if (*jump_target) + { + depth = 1; + break; + } + ++cond_cleanup_depth; + /* If not in the innermost one, next iteration + will handle CLEANUP_BODY similarly. */ + if (depth > 1) + { + s = CLEANUP_BODY (stmt); + break; + } + /* The innermost one can be evaluated normally. */ + cxx_eval_constant_expression (ctx, + CLEANUP_BODY (stmt), + vc_discard, + non_constant_p, + overflow_p, + jump_target); + break; + } + /* And so should be evaluated statements which aren't + last in the STATEMENT_LIST. */ + cxx_eval_constant_expression (ctx, stmt, vc_discard, + non_constant_p, overflow_p, + jump_target); + if (*non_constant_p + || returns (jump_target) + || breaks (jump_target) + || continues (jump_target)) + { + depth = 1; + break; + } + } + } + } + else + cxx_eval_constant_expression (ctx, BIND_EXPR_BODY (cond_prep), + vc_discard, non_constant_p, + overflow_p, jump_target); } if (cond) --- gcc/testsuite/g++.dg/expr/for9.C.jj 2025-02-13 00:20:07.995403443 +0100 +++ gcc/testsuite/g++.dg/expr/for9.C 2025-02-13 00:20:07.995403443 +0100 @@ -0,0 +1,25 @@ +// PR c++/118822 +// { dg-do compile } + +struct A { A (); ~A (); }; +bool baz (); + +void +foo () +{ + while (bool x = baz ()) + { + lab:; + A a; + } +} + +void +bar () +{ + for (bool y = baz (); bool x = baz (); y |= x) + { + lab:; + A a; + } +} --- gcc/testsuite/g++.dg/cpp26/decomp12.C.jj 2025-02-13 00:20:08.019403122 +0100 +++ gcc/testsuite/g++.dg/cpp26/decomp12.C 2025-02-13 00:20:08.019403122 +0100 @@ -0,0 +1,46 @@ +// P0963R3 - Structured binding declaration as a condition +// { dg-do compile { target c++11 } } +// { dg-options "" } + +namespace std { + template<typename T> struct tuple_size; + template<int, typename> struct tuple_element; +} + +struct S { + S () : s (0) {} + S (int x) : s (x) {} + S (const S &x) : s (x.s) {} + ~S () {} + int s; +}; + +struct T { + S a, b, c; + ~T () {} + explicit operator bool () const noexcept { return a.s == b.s; } + template <int I> S get () { return I ? a : b; } +}; + +template<> struct std::tuple_size<T> { static const int value = 2; }; +template<int I> struct std::tuple_element<I,T> { using type = S; }; + +void +foo (T t, bool x) +{ + while (auto [ i, j ] = T { 1, 1, 3 }) // { dg-warning "structured bindings in conditions only available with" "" { target c++23_down } } + { + if (x) + break; + } +} + +void +bar (T t, bool x) +{ + for (int cnt = 0; auto [ i, j ] = T { 2, 2, 4 }; ++cnt) // { dg-warning "structured bindings in conditions only available with" "" { target c++23_down } } + { + if (x) + break; + } +} Jakub