On Sat, Jan 25, 2025 at 10:53:50AM -0500, Jason Merrill wrote:
> On 1/25/25 4:12 AM, Jakub Jelinek wrote:
> > On Fri, Jan 24, 2025 at 07:07:15PM -0500, Jason Merrill wrote:
> > > Hypothetically, but those cases are just either error or DECL_EXTERNAL. In
> > > the error case we're failing anyway; in the external case all the
> > > base/nonbase for a particular structured binding declaration should be
> > > consistent.
> > 
> > So shall I just remove all the prune_vars_needing_no_initialization hunks
> > then or add gcc_checking_assert (!STATIC_INIT_DECOMP_BASE_P (t) &&
> > !STATIC_INIT_DECOMP_NONBASE_P (t)); for the DECL_EXTERNAL punt case?
> 
> Just the assert sounds good.
> 
> > > > Note, unfortunately it is hard to come up with a testcase that actually
> > > > prunes something on purpose...
> > > 
> > > Indeed, it shouldn't be possible.

So like this?

Passed x86_64-linux and i686-linux bootstrap/regtest.

2025-01-26  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): 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.

--- 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-24 18:59:38.918739285 +0100
+++ gcc/cp/decl2.cc     2025-01-25 22:13:50.047641763 +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)
     {
@@ -4611,6 +4666,8 @@ prune_vars_needing_no_initialization (tr
         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 (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 +4718,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 +4758,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 +4769,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 +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);
--- 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

Reply via email to