On Wed, Jan 22, 2025 at 04:19:38PM -0500, Jason Merrill wrote: > > @@ -4482,6 +4536,7 @@ emit_partial_init_fini_fn (bool initp, u > > /* Do one initialization or destruction. */ > > one_static_initialization_or_destruction (initp, decl, init); > > } > > + decomp_finalize_var_list (sl, save_stmts_are_full_exprs_p); > > if (omp_target) > > { > > @@ -4510,6 +4565,7 @@ prune_vars_needing_no_initialization (tr > > { > > tree *var = vars; > > tree result = NULL_TREE; > > + bool clear_nonbase = false; > > while (*var) > > { > > @@ -4517,6 +4573,20 @@ prune_vars_needing_no_initialization (tr > > tree decl = TREE_VALUE (t); > > tree init = TREE_PURPOSE (t); > > + if (STATIC_INIT_DECOMP_BASE_P (t) > > + && result != NULL_TREE > > + && STATIC_INIT_DECOMP_NONBASE_P (result)) > > + clear_nonbase = true; > > + else if (clear_nonbase && !STATIC_INIT_DECOMP_BASE_P (t)) > > + { > > I don't see how we can ever get here... > > > + clear_nonbase = false; > > + for (tree r = result; r; r = TREE_CHAIN (r)) > > + if (STATIC_INIT_DECOMP_NONBASE_P (r)) > > + STATIC_INIT_DECOMP_NONBASE_P (r) = 0; > > + else > > + break; > > + } > > + > > /* Deal gracefully with error. */ > > if (error_operand_p (decl)) > > { > > @@ -4544,6 +4614,28 @@ prune_vars_needing_no_initialization (tr > > continue; > > } > > + clear_nonbase = false; > > ...if we always clear the flag here? What situation is the code in this > function trying to correct? This needs a lot more rationale. Would it be > simpler to do a second loop over result after the main loop?
The purpose of the function is to prune some vars for whatever reason. What cp_finish_decl ensures is that if there are namespace scope structured bindings that need the CWG2867 special handling there is one or more STATIC_INIT_DECOMP_BASE_P {static,tls}_aggregates TREE_LIST (where those correspond to either the artificial base variable or some lifetime extended helpers) followed by (note, here "followed by" really depends on whether the list is reversed or not, e.g. for this function the argument list is reversed and the returned one is not) one or more STATIC_INIT_DECOMP_NONBASE_P TREE_LISTs (these are either the variables corresponding to user identifiers and/or their lifetime extended helpers). As there are only 2 single-bit flags, there is no clear marking which of these base/nonbase VAR_DECLs correspond to structured binding xyz and which correspond to another one. Sure, if those are DECL_DECOMPOSITION_P VAR_DECLs, we can look at base (or self if it is base), but if it is the life extended VAR_DECLs, maybe we could guess something from the mangling, but it becomes horrible. So, all we have is zero or more non-STATIC_INIT_DECOMP*P entries, followed by one or more STATIC_INIT_DECOMP_BASE_P entries, followed by one or more STATIC_INIT_DECOMP_NONBASE_P entries, followed by zero or more non-STATIC_INIT_DECOMP*P entries, perhaps followed by one or more STATIC_INIT_DECOMP_BASE_P entries etc. As there could be zero normal entries in between, the boundary is when STATIC_INIT_DECOMP_NONBASE_P is followed by normal or STATIC_INIT_DECOMP_BASE_P entry. Now if we prune some entries from this list (and sure, reverse it), we could loose the original properties. E.g. if the whole series of consecutive STATIC_INIT_DECOMP_BASE_P entries is pruned and previously it was preceded and followed by STATIC_INIT_DECOMP_NONBASE_P entries, all of sudden the two STATIC_INIT_DECOMP_NONBASE_P sequences are indistinguishable from one and so the cleanups of the STATIC_INIT_DECOMP_BASE_P that are kept could be extended even over the STATIC_INIT_DECOMP_NONBASE_P initialization over which it should not be extended. Similarly, if the whole series of consecutive STATIC_INIT_DECOMP_NONBASE_P entries is pruned and was in between two STATIC_INIT_DECOMP_BASE_P sequences, in the pruned lists those might appear as something for a single structured binding and have all cleanups extended over something it shouldn't be. If from entries corresponding to a single structured binding we drop all STATIC_INIT_DECOMP_BASE_P or all STATIC_INIT_DECOMP_NONBASE_P (or both entries), we just shouldn't have those flags set in any of the remaining entries for that structured binding in the pruned list, because either there are no base VAR_DECLs (so no cleanups to extend across the non-bases) or there are no nonbase VAR_DECLs (and so the base cleanups can just end at the end of the bases). The clear_nonbase flag is part of this pruning. It is not always cleared, in particular it is not cleared in any of the cases where we prune some entries (i.e. if (whatever) { ...; continue; }). if (STATIC_INIT_DECOMP_BASE_P (t) && result != NULL_TREE && STATIC_INIT_DECOMP_NONBASE_P (result)) clear_nonbase = true; sets it to true if we are going to process a base entry and the last entry we've processed was nonbase. The {static,tls}_aggregates ordering is nonbase -> nonbase -> base and we process it in the reverse order as well. Now, if the current entry (t) is not pruned, clear_nonbase is set to false again and nothing happens. Only if we kept a STATIC_INIT_DECOMP_NONBASE_P and then prune one or more STATIC_INIT_DECOMP_BASE_P entries and the next one after all those pruned is not a STATIC_INIT_DECOMP_BASE_P, we do else if (clear_nonbase && !STATIC_INIT_DECOMP_BASE_P (t)) { clear_nonbase = false; for (tree r = result; r; r = TREE_CHAIN (r)) if (STATIC_INIT_DECOMP_NONBASE_P (r)) STATIC_INIT_DECOMP_NONBASE_P (r) = 0; else break; } and in that case clear all the adjacent STATIC_INIT_DECOMP_NONBASE_P from the non-pruned list. So, the clear_nonbase stuff is about dropping STATIC_INIT_DECOMP_NONBASE_P flags in case all the STATIC_INIT_DECOMP_BASE_P entries were pruned. I've added an extra comment and tweaked another one. > > + else if (!STATIC_INIT_DECOMP_BASE_P (t) > > + && !STATIC_INIT_DECOMP_NONBASE_P (t) > > + && result != NULL_TREE > > + && STATIC_INIT_DECOMP_NONBASE_P (result)) > > + { > > + for (tree r = result; r; r = TREE_CHAIN (r)) > > + if (STATIC_INIT_DECOMP_NONBASE_P (r)) > > + STATIC_INIT_DECOMP_NONBASE_P (r) = 0; > > + else > > + break; > > + } Now that I read it once again, I think the above chunk is not needed. Because, in the original chain, STATIC_INIT_DECOMP_NONBASE_P should have been always followed by STATIC_INIT_DECOMP_NONBASE_P or STATIC_INIT_DECOMP_BASE_P, so if we have a normal non-pruned elt and the previous non-pruned elt is STATIC_INIT_DECOMP_NONBASE_P, it means we had to prune at least one STATIC_INIT_DECOMP_BASE_P in between, so clear_nonbase should have been set and not cleared when it was pruned and then we should have encountered with clear_nonbase this t (or something before it that didn't have STATIC_INIT_DECOMP_BASE_P set and should have cleared the flags already). So I've dropped this chunk. Note, unfortunately it is hard to come up with a testcase that actually prunes something on purpose... > > @@ -4560,12 +4652,19 @@ prune_vars_needing_no_initialization (tr > > void > > partition_vars_for_init_fini (tree var_list, priority_map_t *(&parts)[4]) > > { > > + unsigned priority = 0; > > + unsigned decomp_state = 0; > > How about enum { none, base, nonbase } instead of unsigned? Sure, adjusted in the lightly tested patch below (also with the extra comments and one chunk removed). 2025-01-23 Jakub Jelinek <ja...@redhat.com> PR c++/115769 gcc/cp/ * cp-tree.h (STATIC_INIT_DECOMP_BASE_P): Define. (STATIC_INIT_DECOMP_NONBASE_P): Define. * decl.cc (cp_finish_decl): Mark nodes in {static,tls}_aggregates with * decl2.cc (decomp_handle_one_var, decomp_finalize_var_list): New functions. (emit_partial_init_fini_fn): Use them. (prune_vars_needing_no_initialization): Clear STATIC_INIT_DECOMP_*BASE_P flags if needed. (partition_vars_for_init_fini): Use same priority for consecutive STATIC_INIT_DECOMP_*BASE_P vars and propagate those flags to new TREE_LISTs when possible. Formatting fix. (handle_tls_init): Use decomp_handle_one_var and decomp_finalize_var_list functions. gcc/testsuite/ * g++.dg/DRs/dr2867-5.C: New test. * g++.dg/DRs/dr2867-6.C: New test. * g++.dg/DRs/dr2867-7.C: New test. * g++.dg/DRs/dr2867-8.C: New test. --- gcc/cp/cp-tree.h.jj 2024-09-07 09:31:20.601484156 +0200 +++ gcc/cp/cp-tree.h 2024-09-09 15:53:44.924112247 +0200 @@ -470,6 +470,7 @@ extern GTY(()) tree cp_global_trees[CPTI BASELINK_FUNCTIONS_MAYBE_INCOMPLETE_P (in BASELINK) BIND_EXPR_VEC_DTOR (in BIND_EXPR) ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (in ATOMIC_CONSTR) + STATIC_INIT_DECOMP_BASE_P (in the TREE_LIST for {static,tls}_aggregates) 2: IDENTIFIER_KIND_BIT_2 (in IDENTIFIER_NODE) ICS_THIS_FLAG (in _CONV) DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL) @@ -489,6 +490,8 @@ extern GTY(()) tree cp_global_trees[CPTI IMPLICIT_CONV_EXPR_BRACED_INIT (in IMPLICIT_CONV_EXPR) PACK_EXPANSION_AUTO_P (in *_PACK_EXPANSION) contract_semantic (in ASSERTION_, PRECONDITION_, POSTCONDITION_STMT) + STATIC_INIT_DECOMP_NONBASE_P (in the TREE_LIST + for {static,tls}_aggregates) 3: IMPLICIT_RVALUE_P (in NON_LVALUE_EXPR or STATIC_CAST_EXPR) ICS_BAD_FLAG (in _CONV) FN_TRY_BLOCK_P (in TRY_BLOCK) @@ -5947,6 +5950,21 @@ extern bool defer_mangling_aliases; extern bool flag_noexcept_type; +/* True if this TREE_LIST in {static,tls}_aggregates is a for dynamic + initialization of namespace scope structured binding base or related + extended ref init temps. Temporaries from the initialization of + STATIC_INIT_DECOMP_BASE_P dynamic initializers should be destroyed only + after the last STATIC_INIT_DECOMP_NONBASE_P dynamic initializer following + it. */ +#define STATIC_INIT_DECOMP_BASE_P(NODE) \ + TREE_LANG_FLAG_1 (TREE_LIST_CHECK (NODE)) + +/* True if this TREE_LIST in {static,tls}_aggregates is a for dynamic + initialization of namespace scope structured binding non-base + variable using get. */ +#define STATIC_INIT_DECOMP_NONBASE_P(NODE) \ + TREE_LANG_FLAG_2 (TREE_LIST_CHECK (NODE)) + /* A list of namespace-scope objects which have constructors or destructors which reside in the global scope. The decl is stored in the TREE_VALUE slot and the initializer is stored in the --- gcc/cp/decl.cc.jj 2024-09-09 11:50:07.146394047 +0200 +++ gcc/cp/decl.cc 2024-09-09 17:16:26.459094150 +0200 @@ -8485,6 +8485,7 @@ cp_finish_decl (tree decl, tree init, bo bool var_definition_p = false; tree auto_node; auto_vec<tree> extra_cleanups; + tree aggregates1 = NULL_TREE; struct decomp_cleanup { tree decl; cp_decomp *&decomp; @@ -8872,7 +8873,16 @@ cp_finish_decl (tree decl, tree init, bo } if (decomp) - cp_maybe_mangle_decomp (decl, decomp); + { + cp_maybe_mangle_decomp (decl, decomp); + if (TREE_STATIC (decl) && !DECL_FUNCTION_SCOPE_P (decl)) + { + if (CP_DECL_THREAD_LOCAL_P (decl)) + aggregates1 = tls_aggregates; + else + aggregates1 = static_aggregates; + } + } /* If this is a local variable that will need a mangled name, register it now. We must do this before processing the @@ -9210,6 +9220,32 @@ cp_finish_decl (tree decl, tree init, bo if (decomp_init) add_stmt (decomp_init); + if (decomp + && var_definition_p + && TREE_STATIC (decl) + && !DECL_FUNCTION_SCOPE_P (decl)) + { + tree &aggregates3 = (CP_DECL_THREAD_LOCAL_P (decl) + ? tls_aggregates : static_aggregates); + tree aggregates2 = aggregates3; + if (aggregates2 != aggregates1) + { + cp_finish_decomp (decl, decomp); + decomp = NULL; + if (aggregates3 != aggregates2) + { + /* If there are dynamic initializers for the structured + binding base or associated extended ref temps and also + dynamic initializers for the structured binding non-base + vars, mark them. */ + for (tree t = aggregates3; t != aggregates2; t = TREE_CHAIN (t)) + STATIC_INIT_DECOMP_NONBASE_P (t) = 1; + for (tree t = aggregates2; t != aggregates1; t = TREE_CHAIN (t)) + STATIC_INIT_DECOMP_BASE_P (t) = 1; + } + } + } + if (was_readonly) TREE_READONLY (decl) = 1; --- gcc/cp/decl2.cc.jj 2025-01-23 11:10:47.867729601 +0100 +++ gcc/cp/decl2.cc 2025-01-23 15:43:33.515476018 +0100 @@ -4504,6 +4504,55 @@ one_static_initialization_or_destruction DECL_STATIC_FUNCTION_P (current_function_decl) = 0; } +/* Helper function for emit_partial_init_fini_fn and handle_tls_init. + For structured bindings, disable stmts_are_full_exprs_p () + on STATIC_INIT_DECOMP_BASE_P nodes, reenable it on the + first STATIC_INIT_DECOMP_NONBASE_P node and emit all the + STATIC_INIT_DECOMP_BASE_P and STATIC_INIT_DECOMP_NONBASE_P + consecutive nodes in a single STATEMENT_LIST wrapped with + CLEANUP_POINT_EXPR. */ + +static inline tree +decomp_handle_one_var (tree node, tree sl, bool *saw_nonbase, + int save_stmts_are_full_exprs_p) +{ + if (sl && !*saw_nonbase && STATIC_INIT_DECOMP_NONBASE_P (node)) + { + *saw_nonbase = true; + current_stmt_tree ()->stmts_are_full_exprs_p + = save_stmts_are_full_exprs_p; + } + else if (sl && *saw_nonbase && !STATIC_INIT_DECOMP_NONBASE_P (node)) + { + sl = pop_stmt_list (sl); + sl = maybe_cleanup_point_expr_void (sl); + add_stmt (sl); + sl = NULL_TREE; + } + if (sl == NULL_TREE && STATIC_INIT_DECOMP_BASE_P (node)) + { + sl = push_stmt_list (); + *saw_nonbase = false; + current_stmt_tree ()->stmts_are_full_exprs_p = 0; + } + return sl; +} + +/* Similarly helper called when the whole var list is processed. */ + +static inline void +decomp_finalize_var_list (tree sl, int save_stmts_are_full_exprs_p) +{ + if (sl) + { + current_stmt_tree ()->stmts_are_full_exprs_p + = save_stmts_are_full_exprs_p; + sl = pop_stmt_list (sl); + sl = maybe_cleanup_point_expr_void (sl); + add_stmt (sl); + } +} + /* Generate code to do the initialization or destruction of the decls in VARS, a TREE_LIST of VAR_DECL with static storage duration. Whether initialization or destruction is performed is specified by INITP. */ @@ -4533,12 +4582,17 @@ emit_partial_init_fini_fn (bool initp, u finish_if_stmt_cond (target_dev_p, nonhost_if_stmt); } + tree sl = NULL_TREE; + int save_stmts_are_full_exprs_p = stmts_are_full_exprs_p (); + bool saw_nonbase = false; for (tree node = vars; node; node = TREE_CHAIN (node)) { tree decl = TREE_VALUE (node); tree init = TREE_PURPOSE (node); - /* We will emit 'init' twice, and it is modified in-place during - gimplification. Make a copy here. */ + sl = decomp_handle_one_var (node, sl, &saw_nonbase, + save_stmts_are_full_exprs_p); + /* We will emit 'init' twice, and it is modified in-place during + gimplification. Make a copy here. */ if (omp_target) { /* We've already emitted INIT in the host version of the ctor/dtor @@ -4562,6 +4616,7 @@ emit_partial_init_fini_fn (bool initp, u /* Do one initialization or destruction. */ one_static_initialization_or_destruction (initp, decl, init); } + decomp_finalize_var_list (sl, save_stmts_are_full_exprs_p); if (omp_target) { @@ -4590,6 +4645,7 @@ prune_vars_needing_no_initialization (tr { tree *var = vars; tree result = NULL_TREE; + bool clear_nonbase = false; while (*var) { @@ -4597,6 +4653,33 @@ prune_vars_needing_no_initialization (tr tree decl = TREE_VALUE (t); tree init = TREE_PURPOSE (t); + /* cp_finish_decl will for structured bindings which since CWG2867 + need to extend lifetime of the base across the non-base vars + have one or more STATIC_INIT_DECOMP_NONBASE_P elts (across whose + initialization lifetime should be extended) followed by one + or more STATIC_INIT_DECOMP_BASE_P elts (whose lifetime should be + extended). If either all adjacent STATIC_INIT_DECOMP_NONBASE_P + or all adjacent STATIC_INIT_DECOMP_BASE_P elts are pruned, we + need to clear the flags on the rest, as no lifetime extension + is needed. + The following code handles the case where all the adjacent + STATIC_INIT_DECOMP_BASE_P elts are pruned and clears the + STATIC_INIT_DECOMP_NONBASE_P flag on the adjacent elts we've + processed. */ + if (STATIC_INIT_DECOMP_BASE_P (t) + && result != NULL_TREE + && STATIC_INIT_DECOMP_NONBASE_P (result)) + clear_nonbase = true; + else if (clear_nonbase && !STATIC_INIT_DECOMP_BASE_P (t)) + { + clear_nonbase = false; + for (tree r = result; r; r = TREE_CHAIN (r)) + if (STATIC_INIT_DECOMP_NONBASE_P (r)) + STATIC_INIT_DECOMP_NONBASE_P (r) = 0; + else + break; + } + /* Deal gracefully with error. */ if (error_operand_p (decl)) { @@ -4624,6 +4707,17 @@ prune_vars_needing_no_initialization (tr continue; } + clear_nonbase = false; + /* T will not be pruned. If it is STATIC_INIT_DECOMP_BASE_P + and it is not preceded by STATIC_INIT_DECOMP_*BASE_P, it means + all the STATIC_INIT_DECOMP_NONBASE_P entries before it have + been pruned, so clear STATIC_INIT_DECOMP_BASE_P. */ + if (STATIC_INIT_DECOMP_BASE_P (t) + && !(result != NULL_TREE + && (STATIC_INIT_DECOMP_BASE_P (result) + || STATIC_INIT_DECOMP_NONBASE_P (result)))) + STATIC_INIT_DECOMP_BASE_P (t) = 0; + /* This variable is going to need initialization and/or finalization, so we add it to the list. */ *var = TREE_CHAIN (t); @@ -4640,12 +4734,19 @@ prune_vars_needing_no_initialization (tr void partition_vars_for_init_fini (tree var_list, priority_map_t *(&parts)[4]) { + unsigned priority = 0; + enum { none, base, nonbase } decomp_state = none; for (auto node = var_list; node; node = TREE_CHAIN (node)) { tree decl = TREE_VALUE (node); tree init = TREE_PURPOSE (node); bool has_cleanup = !TYPE_HAS_TRIVIAL_DESTRUCTOR (TREE_TYPE (decl)); - unsigned priority = DECL_EFFECTIVE_INIT_PRIORITY (decl); + if (decomp_state == base && STATIC_INIT_DECOMP_NONBASE_P (node)) + decomp_state = nonbase; + else if (decomp_state == nonbase && !STATIC_INIT_DECOMP_NONBASE_P (node)) + decomp_state = none; + if (decomp_state == none) + priority = DECL_EFFECTIVE_INIT_PRIORITY (decl); if (init || (flag_use_cxa_atexit && has_cleanup)) { @@ -4654,6 +4755,34 @@ partition_vars_for_init_fini (tree var_l parts[true] = priority_map_t::create_ggc (); auto &slot = parts[true]->get_or_insert (priority); slot = tree_cons (init, decl, slot); + if (init + && STATIC_INIT_DECOMP_BASE_P (node) + && decomp_state == none) + { + /* If one or more STATIC_INIT_DECOMP_BASE_P with at least + one init is followed by at least one + STATIC_INIT_DECOMP_NONBASE_P with init, mark it in the + resulting chain as well. */ + for (tree n = TREE_CHAIN (node); n; n = TREE_CHAIN (n)) + if (STATIC_INIT_DECOMP_BASE_P (n)) + continue; + else if (STATIC_INIT_DECOMP_NONBASE_P (n)) + { + if (TREE_PURPOSE (n)) + { + decomp_state = base; + break; + } + else + continue; + } + else + break; + } + if (init && decomp_state == base) + STATIC_INIT_DECOMP_BASE_P (slot) = 1; + else if (decomp_state == nonbase) + STATIC_INIT_DECOMP_NONBASE_P (slot) = 1; } if (!flag_use_cxa_atexit && has_cleanup) @@ -4666,7 +4795,7 @@ partition_vars_for_init_fini (tree var_l } if (flag_openmp - && lookup_attribute ("omp declare target", DECL_ATTRIBUTES (decl))) + && lookup_attribute ("omp declare target", DECL_ATTRIBUTES (decl))) { priority_map_t **omp_parts = parts + 2; @@ -4677,6 +4806,10 @@ partition_vars_for_init_fini (tree var_l omp_parts[true] = priority_map_t::create_ggc (); auto &slot = omp_parts[true]->get_or_insert (priority); slot = tree_cons (init, decl, slot); + if (init && decomp_state == base) + STATIC_INIT_DECOMP_BASE_P (slot) = 1; + else if (decomp_state == nonbase) + STATIC_INIT_DECOMP_NONBASE_P (slot) = 1; } if (!flag_use_cxa_atexit && has_cleanup) @@ -5063,10 +5196,15 @@ handle_tls_init (void) finish_expr_stmt (cp_build_modify_expr (loc, guard, NOP_EXPR, boolean_true_node, tf_warning_or_error)); + tree sl = NULL_TREE; + int save_stmts_are_full_exprs_p = stmts_are_full_exprs_p (); + bool saw_nonbase = false; for (; vars; vars = TREE_CHAIN (vars)) { tree var = TREE_VALUE (vars); tree init = TREE_PURPOSE (vars); + sl = decomp_handle_one_var (vars, sl, &saw_nonbase, + save_stmts_are_full_exprs_p); one_static_initialization_or_destruction (/*initp=*/true, var, init); /* Output init aliases even with -fno-extern-tls-init. */ @@ -5081,6 +5219,7 @@ handle_tls_init (void) gcc_assert (alias != NULL); } } + decomp_finalize_var_list (sl, save_stmts_are_full_exprs_p); finish_then_clause (if_stmt); finish_if_stmt (if_stmt); --- gcc/testsuite/g++.dg/DRs/dr2867-5.C.jj 2024-09-09 14:09:22.181185411 +0200 +++ gcc/testsuite/g++.dg/DRs/dr2867-5.C 2024-09-10 10:44:40.859421538 +0200 @@ -0,0 +1,92 @@ +// CWG2867 - Order of initialization for structured bindings. +// { dg-do run { target c++11 } } +// { dg-options "" } + +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0) + +namespace std { + template<typename T> struct tuple_size; + template<int, typename> struct tuple_element; +} + +int a, c, d, i; + +struct A { + A () { assert (c == 3); ++c; } + ~A () { ++a; } + template <int I> int &get () const { assert (c == 5 + I); ++c; return i; } +}; + +template <> struct std::tuple_size <A> { static const int value = 4; }; +template <int I> struct std::tuple_element <I, A> { using type = int; }; +template <> struct std::tuple_size <const A> { static const int value = 4; }; +template <int I> struct std::tuple_element <I, const A> { using type = int; }; + +struct B { + B () { assert (c >= 1 && c <= 2); ++c; } + ~B () { assert (c >= 9 && c <= 10); ++c; } +}; + +struct C { + constexpr C () {} + constexpr C (const C &) {} + template <int I> int &get () const { assert (d == 1 + I); ++d; return i; } +}; + +template <> struct std::tuple_size <C> { static const int value = 3; }; +template <int I> struct std::tuple_element <I, C> { using type = int; }; +template <> struct std::tuple_size <const C> { static const int value = 3; }; +template <int I> struct std::tuple_element <I, const C> { using type = int; }; + +A +foo (const B &, const B &) +{ + A a; + assert (c == 4); + ++c; + return a; +} + +constexpr C +foo (const C &, const C &) +{ + return C {}; +} + +int +bar (int &x, int y) +{ + x = y; + return y; +} + +int +baz (int &x, int y) +{ + assert (x == y); + return y; +} + +struct E { + ~E () { assert (a == 2); } +}; + +E e; +int c1 = bar (c, 1); +const auto &[x, y, z, w] = foo (B {}, B {}); // { dg-warning "structured bindings only available with" "" { target c++14_down } } +int c2 = baz (c, 11); +int d1 = bar (d, 1); +const auto &[s, t, u] = foo (C {}, C {}); // { dg-warning "structured bindings only available with" "" { target c++14_down } } +int d2 = baz (d, 4); +int c3 = bar (c, 1); +auto [x2, y2, z2, w2] = foo (B {}, B {}); // { dg-warning "structured bindings only available with" "" { target c++14_down } } +int c4 = baz (c, 11); +int d3 = bar (d, 1); +auto [s2, t2, u2] = foo (C {}, C {}); // { dg-warning "structured bindings only available with" "" { target c++14_down } } +int d4 = baz (d, 4); + +int +main () +{ + assert (a == 0); +} --- gcc/testsuite/g++.dg/DRs/dr2867-6.C.jj 2024-09-09 14:19:56.455059937 +0200 +++ gcc/testsuite/g++.dg/DRs/dr2867-6.C 2024-09-09 14:56:22.572568526 +0200 @@ -0,0 +1,83 @@ +// CWG2867 - Order of initialization for structured bindings. +// { dg-do run { target c++11 } } +// { dg-options "" } + +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0) + +namespace std { + template<typename T> struct tuple_size; + template<int, typename> struct tuple_element; +} + +int a, c; + +struct C { + C () { assert (c >= 5 && c <= 17 && (c - 5) % 4 == 0); ++c; } + ~C () { assert (c >= 8 && c <= 20 && c % 4 == 0); ++c; } +}; + +struct D { + D () { assert (c >= 7 && c <= 19 && (c - 7) % 4 == 0); ++c; } + ~D () { assert (a % 5 != 4); ++a; } +}; + +struct A { + A () { assert (c == 3); ++c; } + ~A () { assert (a % 5 == 4); ++a; } + template <int I> D get (const C & = C{}) const { assert (c == 6 + 4 * I); ++c; return D {}; } +}; + +template <> struct std::tuple_size <A> { static const int value = 4; }; +template <int I> struct std::tuple_element <I, A> { using type = D; }; +template <> struct std::tuple_size <const A> { static const int value = 4; }; +template <int I> struct std::tuple_element <I, const A> { using type = D; }; + +struct B { + B () { assert (c >= 1 && c <= 2); ++c; } + ~B () { assert (c >= 21 && c <= 22); ++c; } +}; + +A +foo (const B &, const B &) +{ + A a; + assert (c == 4); + ++c; + return a; +} + +int +bar (int &x, int y) +{ + x = y; + return y; +} + +int +baz (int &x, int y) +{ + assert (x == y); + return y; +} + +struct E { + ~E () { assert (a == 5); } +}; + +E e; +int c1 = bar (c, 1); +// First B::B () is invoked twice, then foo called, which invokes A::A (). +// e is reference bound to the A::A () constructed temporary. +// Then 4 times (in increasing I): +// C::C () is invoked, get is called, D::D () is invoked, C::~C () is +// invoked. +// After that B::~B () is invoked twice. +// At exit time D::~D () is invoked 4 times, then A::~A (). +const auto &[x, y, z, w] = foo (B {}, B {}); // { dg-warning "structured bindings only available with" "" { target c++14_down } } +int c2 = baz (c, 23); + +int +main () +{ + assert (a == 0); +} --- gcc/testsuite/g++.dg/DRs/dr2867-7.C.jj 2024-09-10 12:08:07.770933520 +0200 +++ gcc/testsuite/g++.dg/DRs/dr2867-7.C 2024-09-10 12:19:48.730462845 +0200 @@ -0,0 +1,98 @@ +// CWG2867 - Order of initialization for structured bindings. +// { dg-do run { target c++11 } } +// { dg-options "" } +// { dg-add-options tls } +// { dg-require-effective-target tls_runtime } + +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0) + +namespace std { + template<typename T> struct tuple_size; + template<int, typename> struct tuple_element; +} + +int a, c, d, i; + +struct A { + A () { assert (c == 3); ++c; } + ~A () { ++a; } + template <int I> int &get () const { assert (c == 5 + I); ++c; return i; } +}; + +template <> struct std::tuple_size <A> { static const int value = 4; }; +template <int I> struct std::tuple_element <I, A> { using type = int; }; +template <> struct std::tuple_size <const A> { static const int value = 4; }; +template <int I> struct std::tuple_element <I, const A> { using type = int; }; + +struct B { + B () { assert (c >= 1 && c <= 2); ++c; } + ~B () { assert (c >= 9 && c <= 10); ++c; } +}; + +struct C { + constexpr C () {} + constexpr C (const C &) {} + template <int I> int &get () const { assert (d == 1 + I); ++d; return i; } +}; + +template <> struct std::tuple_size <C> { static const int value = 3; }; +template <int I> struct std::tuple_element <I, C> { using type = int; }; +template <> struct std::tuple_size <const C> { static const int value = 3; }; +template <int I> struct std::tuple_element <I, const C> { using type = int; }; + +A +foo (const B &, const B &) +{ + A a; + assert (c == 4); + ++c; + return a; +} + +constexpr C +foo (const C &, const C &) +{ + return C {}; +} + +int +bar (int &x, int y) +{ + x = y; + return y; +} + +int +baz (int &x, int y) +{ + assert (x == y); + return y; +} + +struct E { + ~E () { assert (a == 2); } +}; + +thread_local E e; +thread_local int c1 = bar (c, 1); +thread_local const auto &[x, y, z, w] = foo (B {}, B {}); // { dg-warning "structured bindings only available with" "" { target c++14_down } } +thread_local int c2 = baz (c, 11); // { dg-warning "structured binding declaration can be 'thread_local' only in" "" { target c++17_down } .-1 } +thread_local int d1 = bar (d, 1); +thread_local const auto &[s, t, u] = foo (C {}, C {}); // { dg-warning "structured bindings only available with" "" { target c++14_down } } +thread_local int d2 = baz (d, 4); // { dg-warning "structured binding declaration can be 'thread_local' only in" "" { target c++17_down } .-1 } +thread_local int c3 = bar (c, 1); +thread_local auto [x2, y2, z2, w2] = foo (B {}, B {}); // { dg-warning "structured bindings only available with" "" { target c++14_down } } +thread_local int c4 = baz (c, 11); // { dg-warning "structured binding declaration can be 'thread_local' only in" "" { target c++17_down } .-1 } +thread_local int d3 = bar (d, 1); +thread_local auto [s2, t2, u2] = foo (C {}, C {}); // { dg-warning "structured bindings only available with" "" { target c++14_down } } +thread_local int d4 = baz (d, 4); // { dg-warning "structured binding declaration can be 'thread_local' only in" "" { target c++17_down } .-1 } + +int +main () +{ + volatile int u = c1 + x + y + z + w + c2; + u += d1 + s + t + u + d2; + u += c3 + x2 + y2 + z2 + w2 + c4; + u += d3 + s2 + t2 + u2 + d4; + assert (a == 0); +} --- gcc/testsuite/g++.dg/DRs/dr2867-8.C.jj 2024-09-10 12:09:28.773839087 +0200 +++ gcc/testsuite/g++.dg/DRs/dr2867-8.C 2024-09-10 12:34:06.556878510 +0200 @@ -0,0 +1,86 @@ +// CWG2867 - Order of initialization for structured bindings. +// { dg-do run { target c++11 } } +// { dg-options "" } +// { dg-add-options tls } +// { dg-require-effective-target tls_runtime } + +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0) + +namespace std { + template<typename T> struct tuple_size; + template<int, typename> struct tuple_element; +} + +int a, c; + +struct C { + C () { assert (c >= 5 && c <= 17 && (c - 5) % 4 == 0); ++c; } + ~C () { assert (c >= 8 && c <= 20 && c % 4 == 0); ++c; } +}; + +struct D { + D () { assert (c >= 7 && c <= 19 && (c - 7) % 4 == 0); ++c; } + ~D () { assert (a % 5 != 4); ++a; } +}; + +struct A { + A () { assert (c == 3); ++c; } + ~A () { assert (a % 5 == 4); ++a; } + template <int I> D get (const C & = C{}) const { assert (c == 6 + 4 * I); ++c; return D {}; } +}; + +template <> struct std::tuple_size <A> { static const int value = 4; }; +template <int I> struct std::tuple_element <I, A> { using type = D; }; +template <> struct std::tuple_size <const A> { static const int value = 4; }; +template <int I> struct std::tuple_element <I, const A> { using type = D; }; + +struct B { + B () { assert (c >= 1 && c <= 2); ++c; } + ~B () { assert (c >= 21 && c <= 22); ++c; } +}; + +A +foo (const B &, const B &) +{ + A a; + assert (c == 4); + ++c; + return a; +} + +int +bar (int &x, int y) +{ + x = y; + return y; +} + +int +baz (int &x, int y) +{ + assert (x == y); + return y; +} + +struct E { + ~E () { assert (a == 5); } +}; + +thread_local E e; +thread_local int c1 = bar (c, 1); +// First B::B () is invoked twice, then foo called, which invokes A::A (). +// e is reference bound to the A::A () constructed temporary. +// Then 4 times (in increasing I): +// C::C () is invoked, get is called, D::D () is invoked, C::~C () is +// invoked. +// After that B::~B () is invoked twice. +// At exit time D::~D () is invoked 4 times, then A::~A (). +thread_local const auto &[x, y, z, w] = foo (B {}, B {}); // { dg-warning "structured bindings only available with" "" { target c++14_down } } +thread_local int c2 = baz (c, 23); // { dg-warning "structured binding declaration can be 'thread_local' only in" "" { target c++17_down } .-1 } + +int +main () +{ + volatile int u = c1 + c2; + assert (a == 0); +} Jakub