On Tue, Sep 10, 2024 at 08:29:58PM +0200, Jakub Jelinek wrote: > Hi! > > The following patch on top of the > https://gcc.gnu.org/pipermail/gcc-patches/2024-September/662507.html > patch adds CWG 2867 support for namespace locals. > > Those vars are just pushed into {static,tls}_aggregates chain, then > pruned from those lists, separated by priority and finally emitted into > the corresponding dynamic initialization functions. > The patch adds two flags used on the TREE_LIST nodes in those lists, > one marks the structured binding base variable and/or associated ref > extended temps, another marks the vars initialized using get methods. > The flags are preserved across the pruning, for splitting into by priority > all associated decls of a structured binding using tuple* are forced > into the same priority as the first one, and finally when actually emitting > code, CLEANUP_POINT_EXPRs are disabled in the base initializer(s) and > code from the bases and non-bases together is wrapped into a single > CLEANUP_POINT_EXPR. > > Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? > > Note, I haven't touched the module handling; from what I can see, > prune_vars_needing_no_initialization is destructive to the > {static,tls}_aggregates lists (keeps the list NULL at the end or if there > are errors or it contains some DECL_EXTERNAL decls, keeps in there just > those, not the actual vars that need dynamic initialization) and > the module writing is done only afterwards, so I think it could work > reasonably only if header_module_p (). Can namespace scope structured > bindings appear in header_module_p () or !header_module_p () modules? > How would a testcase using them look like? Especially when structured > bindings can't be extern nor templates nor inline there can be just one > definition, so the module would need to be included in a single file, no?
In the header_module_p case, it is valid to have internal linkage definitions (e.g. in an anonymous namespace), but in that case the {static,tls}_aggregates lists should still be in place to be streamed and everything should work as "normal". (Note that it is 'valid' but not actually supported yet, I have a patch series in progress to fix up all the various linkage issues.) In the !header_module_p case, the modules streaming code doesn't use those lists at all; namespace-scope definitions are attached to the module TU directly and any initialization/destruction code is emitted there. Definitions would only be streamed if the variables are usable in constant expressions. So I don't think there's anything to do for modules here. Yours, Nathaniel > In any case, the patch shouldn't make the modules case any worse, it > just adds TREE_LIST flags which will not be streamed for modules and so > if one can use structured bindings in modules, possibly CWG 2867 would be > not fixed for those but nothing worse than that. > > 2024-09-10 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 2024-09-06 13:43:37.759302079 +0200 > +++ gcc/cp/decl2.cc 2024-09-10 13:04:05.475783972 +0200 > @@ -4424,6 +4424,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. > */ > @@ -4453,12 +4502,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 > @@ -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)) > + { > + 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; > + /* Ensure that in the returned result chain if the > + STATIC_INIT_DECOMP_*BASE_P flags are set, there is always > + one or more STATIC_INIT_DECOMP_BASE_P TREE_LIST followed by > + one or more STATIC_INIT_DECOMP_NONBASE_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; > + 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; > + } > + > /* This variable is going to need initialization and/or > finalization, so we add it to the list. */ > *var = TREE_CHAIN (t); > @@ -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; > 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 == 1 && STATIC_INIT_DECOMP_NONBASE_P (node)) > + decomp_state = 2; > + else if (decomp_state == 2 && !STATIC_INIT_DECOMP_NONBASE_P (node)) > + decomp_state = 0; > + if (!decomp_state) > + priority = DECL_EFFECTIVE_INIT_PRIORITY (decl); > > if (init || (flag_use_cxa_atexit && has_cleanup)) > { > @@ -4574,6 +4673,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 == 0) > + { > + /* 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 = 1; > + break; > + } > + else > + continue; > + } > + else > + break; > + } > + if (init && decomp_state == 1) > + STATIC_INIT_DECOMP_BASE_P (slot) = 1; > + else if (decomp_state == 2) > + STATIC_INIT_DECOMP_NONBASE_P (slot) = 1; > } > > if (!flag_use_cxa_atexit && has_cleanup) > @@ -4586,7 +4713,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; > > @@ -4597,6 +4724,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 == 1) > + STATIC_INIT_DECOMP_BASE_P (slot) = 1; > + else if (decomp_state == 2) > + STATIC_INIT_DECOMP_NONBASE_P (slot) = 1; > } > > if (!flag_use_cxa_atexit && has_cleanup) > @@ -4983,10 +5114,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. */ > @@ -5001,6 +5137,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 >