Hi!

The following patch partially implements CWG 2867
- Order of initialization for structured bindings.
The DR requires that initialization of e is sequenced before r_i and
that r_i initialization is sequenced before r_j for j > i, we already do it
that way, the former ordering is a necessity so that the get calls are
actually emitted on already initialized variable, the rest just because
we implemented it that way, by going through the structured binding
vars in ascending order and doing their initialization.

The hard part not implemented yet is the lifetime extension of the
temporaries from the e initialization to after the get calls (if any).
Unlike the range-for lifetime extension patch which I've posted recently
where IMO we can just ignore lifetime extension of reference bound
temporaries because all the temporaries are extended to the same spot,
here lifetime extension of reference bound temporaries should last until
the end of lifetime of e, while other temporaries only after all the get
calls.

The patch just attempts to deal with automatic structured bindings for now,
I'll post a patch for static locals incrementally and I don't have a patch
for namespace scope structured bindings yet, this patch should just keep
existing behavior for both static locals and namespace scope structured
bindings.

What GCC currently emits is a CLEANUP_POINT_EXPR around the e
initialization, followed optionally by nested CLEANUP_STMTs for cleanups
like the e dtor if any and dtors of lifetime extended temporaries from
reference binding; inside of the CLEANUP_STMT CLEANUP_BODY then the
initialization of the individual variables for the tuple case, again with
optional CLEANUP_STMT if e.g. lifetime extended temporaries from reference
binding are needed in those.

The following patch drops that first CLEANUP_POINT_EXPR and instead
wraps the whole sequence of the e initialization and the individual variable
initialization with get calls after it into a single CLEANUP_POINT_EXPR.
If there are any CLEANUP_STMTs needed, they are all emitted first, with
the CLEANUP_POINT_EXPR for e initialization and the individual variable
initialization inside of those, and a guard variable set after different
phases in those expressions guarding the corresponding cleanups, so that
they aren't invoked until the respective variables are constructed.
This is implemented by cp_finish_decl doing cp_finish_decomp on its own
when !processing_template_decl (otherwise we often don't cp_finish_decl
or process it at a different time from when we want to call
cp_finish_decomp) or unless the decl is erroneous (cp_finish_decl has
too many early returns for erroneous cases, and for those we can actually
call it even multiple times, for the non-erroneous cases
non-processing_template_decl cases we need to call it just once).

The two testcases try to construct various temporaries and variables and
verify the order in which the temporaries and variables are constructed and
destructed.

Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?

2024-08-14  Jakub Jelinek  <ja...@redhat.com>

        PR c++/115769
        * cp-tree.h: Partially implement CWG 2867 - Order of initialization
        for structured bindings.
        (cp_finish_decomp): Add bool argument defaulted to false.
        * decl.cc (initialize_local_var): Add DECOMP argument, if true,
        don't build cleanup and temporarily override stmts_are_full_exprs_p
        to 0 rather than 1.  Formatting fix.
        (cp_finish_decl): Invoke cp_finish_decomp fpr structured bindings
        here if !processing_template_decl, first with TEST_P true.  For
        automatic structured binding bases if the test cp_finish_decomp
        returned true wrap the initialization together with what non-test
        cp_finish_decomp emits with a CLEANUP_POINT_EXPR, and if there are
        any CLEANUP_STMTs needed, emit them around the whole
        CLEANUP_POINT_EXPR with guard variables for the cleanups.
        (cp_finish_decomp): Add TEST_P argument, change return type from
        void to bool, if TEST_P, return true instead of emitting actual
        code for the tuple case, otherwise return false.
        * parser.cc (cp_convert_range_for): Don't call cp_finish_decomp
        unless range_decl is erroneous.
        (cp_parser_decomposition_declaration): Set DECL_DECOMP_BASE
        before cp_finish_decl call, call cp_finish_decomp after it only
        if processing_template_decl or decl is erroneous.
        (cp_finish_omp_range_for): Call cp_finish_decomp only if
        processing_template_decl or decl is erroneous.
        * pt.cc (tsubst_stmt): Likewise.

        * g++.dg/DRs/dr2867-1.C: New test.
        * g++.dg/DRs/dr2867-2.C: New test.

--- gcc/cp/cp-tree.h.jj 2024-08-12 10:49:12.355612381 +0200
+++ gcc/cp/cp-tree.h    2024-08-13 12:54:04.013233029 +0200
@@ -7021,7 +7021,7 @@ extern void omp_declare_variant_finalize
 struct cp_decomp { tree decl; unsigned int count; };
 extern void cp_finish_decl                     (tree, tree, bool, tree, int, 
cp_decomp * = nullptr);
 extern tree lookup_decomp_type                 (tree);
-extern void cp_finish_decomp                   (tree, cp_decomp *);
+extern bool cp_finish_decomp                   (tree, cp_decomp *, bool = 
false);
 extern int cp_complete_array_type              (tree *, tree, bool);
 extern int cp_complete_array_type_or_error     (tree *, tree, bool, 
tsubst_flags_t);
 extern tree build_ptrmemfunc_type              (tree);
--- gcc/cp/decl.cc.jj   2024-08-12 11:31:06.046359452 +0200
+++ gcc/cp/decl.cc      2024-08-13 19:18:42.170052535 +0200
@@ -102,7 +102,7 @@ static tree check_special_function_retur
 static tree push_cp_library_fn (enum tree_code, tree, int);
 static tree build_cp_library_fn (tree, enum tree_code, tree, int);
 static void store_parm_decls (tree);
-static void initialize_local_var (tree, tree);
+static void initialize_local_var (tree, tree, bool);
 static void expand_static_init (tree, tree);
 static location_t smallest_type_location (const cp_decl_specifier_seq*);
 static bool identify_goto (tree, location_t, const location_t *,
@@ -8049,14 +8049,13 @@ wrap_temporary_cleanups (tree init, tree
 /* Generate code to initialize DECL (a local variable).  */
 
 static void
-initialize_local_var (tree decl, tree init)
+initialize_local_var (tree decl, tree init, bool decomp)
 {
   tree type = TREE_TYPE (decl);
   tree cleanup;
   int already_used;
 
-  gcc_assert (VAR_P (decl)
-             || TREE_CODE (decl) == RESULT_DECL);
+  gcc_assert (VAR_P (decl) || TREE_CODE (decl) == RESULT_DECL);
   gcc_assert (!TREE_STATIC (decl));
 
   if (DECL_SIZE (decl) == NULL_TREE)
@@ -8076,7 +8075,8 @@ initialize_local_var (tree decl, tree in
     DECL_READ_P (decl) = 1;
 
   /* Generate a cleanup, if necessary.  */
-  cleanup = cxx_maybe_build_cleanup (decl, tf_warning_or_error);
+  cleanup = (decomp ? NULL_TREE
+            : cxx_maybe_build_cleanup (decl, tf_warning_or_error));
 
   /* Perform the initialization.  */
   if (init)
@@ -8111,10 +8111,16 @@ initialize_local_var (tree decl, tree in
 
          gcc_assert (building_stmt_list_p ());
          saved_stmts_are_full_exprs_p = stmts_are_full_exprs_p ();
-         current_stmt_tree ()->stmts_are_full_exprs_p = 1;
+         /* Avoid CLEANUP_POINT_EXPR for the structured binding
+            bases, those will have CLEANUP_POINT_EXPR at the end of
+            code emitted by cp_finish_decomp.  */
+         if (decomp)
+           current_stmt_tree ()->stmts_are_full_exprs_p = 0;
+         else
+           current_stmt_tree ()->stmts_are_full_exprs_p = 1;
          finish_expr_stmt (init);
-         current_stmt_tree ()->stmts_are_full_exprs_p =
-           saved_stmts_are_full_exprs_p;
+         current_stmt_tree ()->stmts_are_full_exprs_p
+           = saved_stmts_are_full_exprs_p;
        }
     }
 
@@ -8437,6 +8443,7 @@ cp_finish_decl (tree decl, tree init, bo
   int was_readonly = 0;
   bool var_definition_p = false;
   tree auto_node;
+  auto_vec<tree> extra_cleanups;
 
   if (decl == error_mark_node)
     return;
@@ -8923,6 +8930,8 @@ cp_finish_decl (tree decl, tree init, bo
       add_decl_expr (decl);
     }
 
+  bool need_decomp_init = false;
+  tree decomp_init = NULL_TREE;
   /* Let the middle end know about variables and functions -- but not
      static data members in uninstantiated class templates.  */
   if (VAR_OR_FUNCTION_DECL_P (decl))
@@ -8984,6 +8993,9 @@ cp_finish_decl (tree decl, tree init, bo
       if (var_definition_p)
        abstract_virtuals_error (decl, type);
 
+      if (decomp && !processing_template_decl)
+       need_decomp_init = cp_finish_decomp (decl, decomp, true);
+
       if (TREE_TYPE (decl) == error_mark_node)
        /* No initialization required.  */
        ;
@@ -9016,8 +9028,89 @@ cp_finish_decl (tree decl, tree init, bo
        }
       /* A variable definition.  */
       else if (DECL_FUNCTION_SCOPE_P (decl) && !TREE_STATIC (decl))
-       /* Initialize the local variable.  */
-       initialize_local_var (decl, init);
+       {
+         /* Initialize the local variable.  */
+         if (!need_decomp_init)
+           initialize_local_var (decl, init, false);
+         else
+           {
+             tree cleanup = NULL_TREE;
+             if (DECL_SIZE (decl))
+               cleanup = cxx_maybe_build_cleanup (decl, tf_warning_or_error);
+             /* If cp_finish_decomp needs to emit any code, we need to emit 
that
+                code after code emitted by initialize_local_var in a single
+                CLEANUP_POINT_EXPR, so that temporaries are destructed only
+                after the cp_finish_decomp emitted code.
+                If there are any cleanups, either extend_ref_init_temps
+                created ones or e.g. array destruction, push those first
+                with the cleanups guarded on a bool temporary, initially
+                set to false and set to true after initialize_local_var
+                emitted code.  */
+             tree guard = NULL_TREE;
+             if (cleanups || cleanup)
+               {
+                 guard = force_target_expr (boolean_type_node,
+                                            boolean_false_node, tf_none);
+                 add_stmt (guard);
+                 guard = TARGET_EXPR_SLOT (guard);
+               }
+             tree sl = push_stmt_list ();
+             initialize_local_var (decl, init, true);
+             if (guard)
+               {
+                 add_stmt (build2 (MODIFY_EXPR, boolean_type_node,
+                                   guard, boolean_true_node));
+                 for (tree &t : *cleanups)
+                   t = build3 (COND_EXPR, void_type_node,
+                               guard, t, void_node);
+                 if (cleanup)
+                   cleanup = build3 (COND_EXPR, void_type_node,
+                                     guard, cleanup, void_node);
+               }
+             unsigned before = stmt_list_stack->length ();
+             cp_finish_decomp (decl, decomp);
+             unsigned n_extra_cleanups = stmt_list_stack->length () - before;
+             sl = pop_stmt_list (sl);
+             need_decomp_init = false;
+             if (n_extra_cleanups)
+               {
+                 /* If cp_finish_decomp needs any cleanups, such as for
+                    extend_ref_init_temps created vars, pop_stmt_list
+                    popped that all, so push those extra cleanups around
+                    the whole sequence with a guard variable.  */
+                 gcc_assert (TREE_CODE (sl) == STATEMENT_LIST);
+                 guard = force_target_expr (integer_type_node,
+                                            integer_zero_node, tf_none);
+                 add_stmt (guard);
+                 guard = TARGET_EXPR_SLOT (guard);
+                 for (unsigned i = 0; i < n_extra_cleanups; ++i)
+                   {
+                     tree_stmt_iterator tsi = tsi_last (sl);
+                     gcc_assert (!tsi_end_p (tsi));
+                     tree last = tsi_stmt (tsi);
+                     gcc_assert (TREE_CODE (last) == CLEANUP_STMT
+                                 && !CLEANUP_EH_ONLY (last));
+                     tree cst = build_int_cst (integer_type_node, i + 1);
+                     tree cl = build3 (COND_EXPR, void_type_node,
+                                       build2 (GE_EXPR, boolean_type_node,
+                                               guard, cst),
+                                       CLEANUP_EXPR (last), void_node);
+                     extra_cleanups.safe_push (cl);
+                     tsi_link_before (&tsi, build2 (MODIFY_EXPR,
+                                                    integer_type_node,
+                                                    guard, cst),
+                                      TSI_SAME_STMT);
+                     tree sl2 = CLEANUP_BODY (last);
+                     gcc_assert (TREE_CODE (sl2) == STATEMENT_LIST);
+                     tsi_link_before (&tsi, sl2, TSI_SAME_STMT);
+                     tsi_delink (&tsi);
+                   }
+               }
+             decomp_init = maybe_cleanup_point_expr_void (sl);
+             if (cleanup)
+               finish_decl_cleanup (decl, cleanup);
+           }
+       }
 
       /* If a variable is defined, and then a subsequent
         definition with external linkage is encountered, we will
@@ -9044,6 +9137,14 @@ cp_finish_decl (tree decl, tree init, bo
       release_tree_vector (cleanups);
     }
 
+  for (tree t : &extra_cleanups)
+    push_cleanup (NULL_TREE, t, false);
+
+  if (decomp_init)
+    add_stmt (decomp_init);
+  else if (need_decomp_init)
+    cp_finish_decomp (decl, decomp);
+
   if (was_readonly)
     TREE_READONLY (decl) = 1;
 
@@ -9326,10 +9427,11 @@ cp_maybe_mangle_decomp (tree decl, cp_de
 /* Finish a decomposition declaration.  DECL is the underlying declaration
    "e", FIRST is the head of a chain of decls for the individual identifiers
    chained through DECL_CHAIN in reverse order and COUNT is the number of
-   those decls.  */
+   those decls.  If TEST_P is true, return true if any code would need to be
+   actually emitted but don't emit it.  Return false otherwise.  */
 
-void
-cp_finish_decomp (tree decl, cp_decomp *decomp)
+bool
+cp_finish_decomp (tree decl, cp_decomp *decomp, bool test_p)
 {
   tree first = decomp->decl;
   unsigned count = decomp->count;
@@ -9348,7 +9450,7 @@ cp_finish_decomp (tree decl, cp_decomp *
        }
       if (DECL_P (decl) && DECL_NAMESPACE_SCOPE_P (decl))
        SET_DECL_ASSEMBLER_NAME (decl, get_identifier ("<decomp>"));
-      return;
+      return false;
     }
 
   location_t loc = DECL_SOURCE_LOCATION (decl);
@@ -9372,7 +9474,7 @@ cp_finish_decomp (tree decl, cp_decomp *
            fit_decomposition_lang_decl (first, decl);
          first = DECL_CHAIN (first);
        }
-      return;
+      return false;
     }
 
   auto_vec<tree, 16> v;
@@ -9514,6 +9616,8 @@ cp_finish_decomp (tree decl, cp_decomp *
       eltscnt = tree_to_uhwi (tsize);
       if (count != eltscnt)
        goto cnt_mismatch;
+      if (test_p)
+       return true;
       if (!processing_template_decl && DECL_DECOMP_BASE (decl))
        {
          /* For structured bindings used in conditions we need to evaluate
@@ -9665,6 +9769,7 @@ cp_finish_decomp (tree decl, cp_decomp *
            DECL_HAS_VALUE_EXPR_P (v[i]) = 1;
          }
     }
+  return false;
 }
 
 /* Returns a declaration for a VAR_DECL as if:
--- gcc/cp/parser.cc.jj 2024-08-12 11:31:06.044359478 +0200
+++ gcc/cp/parser.cc    2024-08-12 20:18:56.451926147 +0200
@@ -14500,7 +14500,7 @@ cp_convert_range_for (tree statement, tr
   cp_finish_decl (range_decl, deref_begin,
                  /*is_constant_init*/false, NULL_TREE,
                  LOOKUP_ONLYCONVERTING, decomp);
-  if (DECL_DECOMPOSITION_P (range_decl))
+  if (DECL_DECOMPOSITION_P (range_decl) && error_operand_p (range_decl))
     cp_finish_decomp (range_decl, decomp);
 
   warn_for_range_copy (range_decl, deref_begin);
@@ -16416,13 +16416,14 @@ cp_parser_decomposition_declaration (cp_
       if (decl != error_mark_node)
        {
          cp_decomp decomp = { prev, cnt };
-         cp_finish_decl (decl, initializer, non_constant_p, NULL_TREE,
-                         (is_direct_init ? LOOKUP_NORMAL : LOOKUP_IMPLICIT),
-                         &decomp);
          if (keyword != RID_MAX)
            DECL_DECOMP_BASE (decl)
              = keyword == RID_SWITCH ? integer_one_node : integer_zero_node;
-         cp_finish_decomp (decl, &decomp);
+         cp_finish_decl (decl, initializer, non_constant_p, NULL_TREE,
+                         (is_direct_init ? LOOKUP_NORMAL : LOOKUP_IMPLICIT),
+                         &decomp);
+         if (processing_template_decl || error_operand_p (decl))
+           cp_finish_decomp (decl, &decomp);
        }
     }
   else if (decl != error_mark_node)
@@ -44782,7 +44783,8 @@ cp_finish_omp_range_for (tree orig, tree
                                        NULL_TREE, tf_warning_or_error),
                  /*is_constant_init*/false, NULL_TREE,
                  LOOKUP_ONLYCONVERTING, decomp);
-  if (DECL_DECOMPOSITION_P (decl))
+  if (DECL_DECOMPOSITION_P (decl)
+      && (processing_template_decl || error_operand_p (decl)))
     cp_finish_decomp (decl, decomp);
 }
 
--- gcc/cp/pt.cc.jj     2024-08-12 11:31:06.053359363 +0200
+++ gcc/cp/pt.cc        2024-08-12 20:19:50.848239513 +0200
@@ -18660,7 +18660,9 @@ tsubst_stmt (tree t, tree args, tsubst_f
                    cp_finish_decl (decl, init, const_init, asmspec_tree, 0,
                                    decomp);
 
-                   if (ndecl != error_mark_node)
+                   if (ndecl != error_mark_node
+                       && (processing_template_decl
+                           || error_operand_p (decl)))
                      cp_finish_decomp (ndecl, decomp);
                  }
              }
--- gcc/testsuite/g++.dg/DRs/dr2867-1.C.jj      2024-08-12 16:55:39.027211248 
+0200
+++ gcc/testsuite/g++.dg/DRs/dr2867-1.C 2024-08-13 12:35:39.372021733 +0200
@@ -0,0 +1,153 @@
+// 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 c, d, i;
+
+struct A {
+  A () { assert (c == 3); ++c; }
+  ~A () { assert (c == 12); ++c; }
+  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
+foo (const int &, const int &)
+{
+  assert (false);
+}
+
+void
+bar ()
+{
+  c = 1;
+  const auto &[x, y, z, w] = foo (B {}, B {}); // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (c == 11);
+  ++c;
+  d = 1;
+  const auto &[s, t, u] = foo (C {}, C {});    // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (d == 4);
+}
+
+template <int N>
+void
+baz ()
+{
+  c = 1;
+  const auto &[x, y, z, w] = foo (B {}, B {}); // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (c == 11);
+  ++c;
+  d = 1;
+  const auto &[s, t, u] = foo (C {}, C {});    // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (d == 4);
+}
+
+template <typename T, typename U>
+void
+qux ()
+{
+  c = 1;
+  const auto &[x, y, z, w] = foo (T {}, T {}); // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (c == 11);
+  ++c;
+  d = 1;
+  const auto &[s, t, u] = foo (U {}, U {});    // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (d == 4);
+}
+
+void
+corge ()
+{
+  c = 1;
+  auto [x, y, z, w] = foo (B {}, B {});        // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (c == 11);
+  ++c;
+  d = 1;
+  auto [s, t, u] = foo (C {}, C {});   // { dg-warning "structured bindings 
only available with" "" { target c++14_down } }
+  assert (d == 4);
+}
+
+template <int N>
+void
+garply ()
+{
+  c = 1;
+  auto [x, y, z, w] = foo (B {}, B {});        // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (c == 11);
+  ++c;
+  d = 1;
+  auto [s, t, u] = foo (C {}, C {});   // { dg-warning "structured bindings 
only available with" "" { target c++14_down } }
+  assert (d == 4);
+}
+
+template <typename T, typename U>
+void
+freddy ()
+{
+  c = 1;
+  auto [x, y, z, w] = foo (T {}, T {});        // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (c == 11);
+  ++c;
+  d = 1;
+  auto [s, t, u] = foo (U {}, U {});   // { dg-warning "structured bindings 
only available with" "" { target c++14_down } }
+  assert (d == 4);
+}
+
+int
+main ()
+{
+  bar ();
+  assert (c == 13);
+  baz <0> ();
+  assert (c == 13);
+  qux <B, C> ();
+  assert (c == 13);
+  corge ();
+  assert (c == 13);
+  garply <42> ();
+  assert (c == 13);
+  freddy <B, C> ();
+  assert (c == 13);
+}
--- gcc/testsuite/g++.dg/DRs/dr2867-2.C.jj      2024-08-13 16:23:57.828317014 
+0200
+++ gcc/testsuite/g++.dg/DRs/dr2867-2.C 2024-08-13 16:23:51.849391735 +0200
@@ -0,0 +1,101 @@
+// 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 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 (c >= 24 && c <= 27); ++c; }
+};
+
+struct A {
+  A () { assert (c == 3); ++c; }
+  ~A () { assert (c == 28); ++c; }
+  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
+foo (const int &, const int &)
+{
+  assert (false);
+}
+
+void
+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, then the following 2 user
+  // statements.
+  // Then 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 } }
+  assert (c == 23);
+  ++c;
+}
+
+template <int N>
+void
+baz ()
+{
+  c = 1;
+  const auto &[x, y, z, w] = foo (B {}, B {}); // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (c == 23);
+  ++c;
+}
+
+template <typename T>
+void
+qux ()
+{
+  c = 1;
+  const auto &[x, y, z, w] = foo (T {}, T {}); // { dg-warning "structured 
bindings only available with" "" { target c++14_down } }
+  assert (c == 23);
+  ++c;
+}
+
+int
+main ()
+{
+  bar ();
+  assert (c == 29);
+  baz <42> ();
+  assert (c == 29);
+  qux <B> ();
+  assert (c == 29);
+}

        Jakub

Reply via email to