https://gcc.gnu.org/g:ad8e6a4ada0a7ed73ac99404ff2b41ed9dc5e940
commit r15-7230-gad8e6a4ada0a7ed73ac99404ff2b41ed9dc5e940 Author: Jakub Jelinek <ja...@redhat.com> Date: Mon Jan 27 16:45:56 2025 +0100 c++: Implement for namespace statics CWG 2867 - Order of initialization for structured bindings [PR115769] The following 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. 2025-01-27 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 emitted for namespace scope structured bindings with STATIC_INIT_DECOMP_{,NON}BASE_P flags when needed. * decl2.cc (decomp_handle_one_var, decomp_finalize_var_list): New functions. (emit_partial_init_fini_fn): Use them. (prune_vars_needing_no_initialization): Assert STATIC_INIT_DECOMP_*BASE_P is not set on DECL_EXTERNAL vars to be pruned out. (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. Diff: --- gcc/cp/cp-tree.h | 18 ++++++ gcc/cp/decl.cc | 38 ++++++++++++- gcc/cp/decl2.cc | 110 ++++++++++++++++++++++++++++++++++-- gcc/testsuite/g++.dg/DRs/dr2867-5.C | 92 ++++++++++++++++++++++++++++++ gcc/testsuite/g++.dg/DRs/dr2867-6.C | 83 +++++++++++++++++++++++++++ gcc/testsuite/g++.dg/DRs/dr2867-7.C | 98 ++++++++++++++++++++++++++++++++ gcc/testsuite/g++.dg/DRs/dr2867-8.C | 86 ++++++++++++++++++++++++++++ 7 files changed, 520 insertions(+), 5 deletions(-) diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 4aa9f9f9aa91..d3c573f064a3 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -472,6 +472,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; 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) @@ -491,6 +492,8 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX]; 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) @@ -6047,6 +6050,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 diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index d6fb76e70345..a0e3c9f25ba1 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -8742,6 +8742,7 @@ cp_finish_decl (tree decl, tree init, bool init_const_expr_p, 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; @@ -9129,7 +9130,16 @@ cp_finish_decl (tree decl, tree init, bool init_const_expr_p, } 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 @@ -9467,6 +9477,32 @@ cp_finish_decl (tree decl, tree init, bool init_const_expr_p, 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; diff --git a/gcc/cp/decl2.cc b/gcc/cp/decl2.cc index 9e61afd359fc..a11c431758f7 100644 --- a/gcc/cp/decl2.cc +++ b/gcc/cp/decl2.cc @@ -4504,6 +4504,55 @@ one_static_initialization_or_destruction (bool initp, tree decl, tree init) 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, unsigned priority, tree vars, 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, unsigned priority, tree vars, /* 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) { @@ -4611,6 +4666,8 @@ prune_vars_needing_no_initialization (tree *vars) here. */ if (DECL_EXTERNAL (decl)) { + gcc_checking_assert (!STATIC_INIT_DECOMP_BASE_P (t) + && !STATIC_INIT_DECOMP_NONBASE_P (t)); var = &TREE_CHAIN (t); continue; } @@ -4640,12 +4697,19 @@ prune_vars_needing_no_initialization (tree *vars) 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 +4718,34 @@ partition_vars_for_init_fini (tree var_list, priority_map_t *(&parts)[4]) 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 +4758,7 @@ partition_vars_for_init_fini (tree var_list, priority_map_t *(&parts)[4]) } 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 +4769,10 @@ partition_vars_for_init_fini (tree var_list, priority_map_t *(&parts)[4]) 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 +5159,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 +5182,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); diff --git a/gcc/testsuite/g++.dg/DRs/dr2867-5.C b/gcc/testsuite/g++.dg/DRs/dr2867-5.C new file mode 100644 index 000000000000..1e4690dd4849 --- /dev/null +++ b/gcc/testsuite/g++.dg/DRs/dr2867-5.C @@ -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); +} diff --git a/gcc/testsuite/g++.dg/DRs/dr2867-6.C b/gcc/testsuite/g++.dg/DRs/dr2867-6.C new file mode 100644 index 000000000000..1556661d5c2e --- /dev/null +++ b/gcc/testsuite/g++.dg/DRs/dr2867-6.C @@ -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); +} diff --git a/gcc/testsuite/g++.dg/DRs/dr2867-7.C b/gcc/testsuite/g++.dg/DRs/dr2867-7.C new file mode 100644 index 000000000000..701200613d73 --- /dev/null +++ b/gcc/testsuite/g++.dg/DRs/dr2867-7.C @@ -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); +} diff --git a/gcc/testsuite/g++.dg/DRs/dr2867-8.C b/gcc/testsuite/g++.dg/DRs/dr2867-8.C new file mode 100644 index 000000000000..477444009773 --- /dev/null +++ b/gcc/testsuite/g++.dg/DRs/dr2867-8.C @@ -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); +}