c++, v3: Partially implement CWG 2867 - Order of initialization for structured bindings [PR115769]
Checks
Context |
Check |
Description |
linaro-tcwg-bot/tcwg_gcc_build--master-arm |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_gcc_check--master-arm |
success
|
Test passed
|
linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 |
success
|
Build passed
|
linaro-tcwg-bot/tcwg_gcc_check--master-aarch64 |
success
|
Test passed
|
Commit Message
On Wed, Sep 04, 2024 at 01:22:47PM -0400, Jason Merrill wrote:
> > @@ -8985,6 +9003,13 @@ 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 (!need_decomp_init)
> > + decomp_cl.decomp = NULL;
>
>
> It seems like all tests of need_decomp_init could instead test
> decomp_cl.decomp. And if we make that field a reference to the decomp
> parameter, we could refer to the parameter instead of ever referring to
> decomp_cl.
So like this (so far quickly tested on *decomp* dr2867*, full
bootstrap/regtest queued)?
2024-09-04 Jakub Jelinek <jakub@redhat.com>
PR c++/115769
* cp-tree.h: Partially implement CWG 2867 - Order of initialization
for structured bindings.
(cp_finish_decomp): Add TEST_P 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. 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. Call
cp_finish_decomp using RAII if not called with decomp != NULL
otherwise.
(cp_finish_decomp): Add TEST_P argument, change return type from
void to bool, if TEST_P is true, 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
after cp_finish_decl.
(cp_parser_decomposition_declaration): Set DECL_DECOMP_BASE
before cp_finish_decl call. Don't call cp_finish_decomp after
cp_finish_decl.
(cp_finish_omp_range_for): Don't call cp_finish_decomp after
cp_finish_decl.
* pt.cc (tsubst_stmt): Likewise.
* g++.dg/DRs/dr2867-1.C: New test.
* g++.dg/DRs/dr2867-2.C: New test.
Jakub
Comments
On 9/4/24 2:03 PM, Jakub Jelinek wrote:
> On Wed, Sep 04, 2024 at 01:22:47PM -0400, Jason Merrill wrote:
>>> @@ -8985,6 +9003,13 @@ 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 (!need_decomp_init)
>>> + decomp_cl.decomp = NULL;
>>
>>
>> It seems like all tests of need_decomp_init could instead test
>> decomp_cl.decomp. And if we make that field a reference to the decomp
>> parameter, we could refer to the parameter instead of ever referring to
>> decomp_cl.
>
> So like this (so far quickly tested on *decomp* dr2867*, full
> bootstrap/regtest queued)?
>
> 2024-09-04 Jakub Jelinek <jakub@redhat.com>
>
> PR c++/115769
> * cp-tree.h: Partially implement CWG 2867 - Order of initialization
> for structured bindings.
> (cp_finish_decomp): Add TEST_P 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. 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. Call
> cp_finish_decomp using RAII if not called with decomp != NULL
> otherwise.
> (cp_finish_decomp): Add TEST_P argument, change return type from
> void to bool, if TEST_P is true, 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
> after cp_finish_decl.
> (cp_parser_decomposition_declaration): Set DECL_DECOMP_BASE
> before cp_finish_decl call. Don't call cp_finish_decomp after
> cp_finish_decl.
> (cp_finish_omp_range_for): Don't call cp_finish_decomp after
> cp_finish_decl.
> * 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-30 09:09:45.466623869 +0200
> +++ gcc/cp/cp-tree.h 2024-08-30 11:00:39.861747964 +0200
> @@ -7024,7 +7024,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-30 09:09:45.495623494 +0200
> +++ gcc/cp/decl.cc 2024-09-04 19:55:59.046491602 +0200
> @@ -103,7 +103,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 *,
> @@ -8058,14 +8058,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)
> @@ -8085,7 +8084,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)
> @@ -8120,10 +8120,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;
> }
> }
>
> @@ -8446,6 +8452,16 @@ 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;
> + struct decomp_cleanup {
> + tree decl;
> + cp_decomp *&decomp;
> + ~decomp_cleanup ()
> + {
> + if (decomp && DECL_DECOMPOSITION_P (decl))
> + cp_finish_decomp (decl, decomp);
> + }
> + } decomp_cl = { decl, decomp };
>
> if (decl == error_mark_node)
> return;
> @@ -8932,6 +8948,7 @@ cp_finish_decl (tree decl, tree init, bo
> add_decl_expr (decl);
> }
>
> + 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))
> @@ -8993,6 +9010,11 @@ cp_finish_decl (tree decl, tree init, bo
> if (var_definition_p)
> abstract_virtuals_error (decl, type);
>
> + if (decomp
> + && !processing_template_decl
> + && !cp_finish_decomp (decl, decomp, true))
It looks like when processing_template_decl this patch will do the
expanded cleanup below, unlike the v2 patch.
> + decomp = NULL;
> +
> if (TREE_TYPE (decl) == error_mark_node)
> /* No initialization required. */
> ;
> @@ -9025,8 +9047,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 (!decomp)
> + 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);
> + decomp = NULL;
> + unsigned n_extra_cleanups = stmt_list_stack->length () - before;
> + sl = pop_stmt_list (sl);
> + 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
> @@ -9053,6 +9156,12 @@ 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);
> +
> if (was_readonly)
> TREE_READONLY (decl) = 1;
>
> @@ -9335,10 +9444,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;
> @@ -9357,7 +9467,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);
> @@ -9381,7 +9491,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;
> @@ -9523,6 +9633,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
> @@ -9674,6 +9786,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-30 09:40:26.583785645 +0200
> +++ gcc/cp/parser.cc 2024-08-30 11:13:18.612089567 +0200
> @@ -14513,8 +14513,6 @@ 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))
> - cp_finish_decomp (range_decl, decomp);
>
> warn_for_range_copy (range_decl, deref_begin);
>
> @@ -16429,13 +16427,12 @@ 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);
> }
> }
> else if (decl != error_mark_node)
> @@ -44810,8 +44807,6 @@ 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))
> - cp_finish_decomp (decl, decomp);
> }
>
> /* Return true if next tokens contain a standard attribute that contains
> --- gcc/cp/pt.cc.jj 2024-08-30 09:09:45.608622030 +0200
> +++ gcc/cp/pt.cc 2024-08-30 11:16:33.291577816 +0200
> @@ -18633,7 +18633,6 @@ tsubst_stmt (tree t, tree args, tsubst_f
> {
> bool const_init = false;
> cp_decomp decomp_d, *decomp = NULL;
> - tree ndecl = error_mark_node;
> tree asmspec_tree = NULL_TREE;
> maybe_push_decl (decl);
>
> @@ -18646,8 +18645,10 @@ tsubst_stmt (tree t, tree args, tsubst_f
> && TREE_TYPE (pattern_decl) != error_mark_node)
> {
> decomp = &decomp_d;
> - ndecl = tsubst_decomp_names (decl, pattern_decl, args,
> - complain, in_decl, decomp);
> + if (tsubst_decomp_names (decl, pattern_decl, args,
> + complain, in_decl, decomp)
> + == error_mark_node)
> + decomp = NULL;
> }
>
> init = tsubst_init (init, decl, args, complain, in_decl);
> @@ -18674,9 +18675,6 @@ tsubst_stmt (tree t, tree args, tsubst_f
>
> cp_finish_decl (decl, init, const_init, asmspec_tree, 0,
> decomp);
> -
> - if (ndecl != error_mark_node)
> - cp_finish_decomp (ndecl, decomp);
> }
> }
> }
> --- gcc/testsuite/g++.dg/DRs/dr2867-1.C.jj 2024-08-30 10:02:41.838454529 +0200
> +++ gcc/testsuite/g++.dg/DRs/dr2867-1.C 2024-08-30 10:02:41.838454529 +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-30 10:02:41.838454529 +0200
> +++ gcc/testsuite/g++.dg/DRs/dr2867-2.C 2024-08-30 10:02:41.838454529 +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
>
@@ -7024,7 +7024,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);
@@ -103,7 +103,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 *,
@@ -8058,14 +8058,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)
@@ -8085,7 +8084,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)
@@ -8120,10 +8120,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;
}
}
@@ -8446,6 +8452,16 @@ 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;
+ struct decomp_cleanup {
+ tree decl;
+ cp_decomp *&decomp;
+ ~decomp_cleanup ()
+ {
+ if (decomp && DECL_DECOMPOSITION_P (decl))
+ cp_finish_decomp (decl, decomp);
+ }
+ } decomp_cl = { decl, decomp };
if (decl == error_mark_node)
return;
@@ -8932,6 +8948,7 @@ cp_finish_decl (tree decl, tree init, bo
add_decl_expr (decl);
}
+ 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))
@@ -8993,6 +9010,11 @@ cp_finish_decl (tree decl, tree init, bo
if (var_definition_p)
abstract_virtuals_error (decl, type);
+ if (decomp
+ && !processing_template_decl
+ && !cp_finish_decomp (decl, decomp, true))
+ decomp = NULL;
+
if (TREE_TYPE (decl) == error_mark_node)
/* No initialization required. */
;
@@ -9025,8 +9047,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 (!decomp)
+ 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);
+ decomp = NULL;
+ unsigned n_extra_cleanups = stmt_list_stack->length () - before;
+ sl = pop_stmt_list (sl);
+ 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
@@ -9053,6 +9156,12 @@ 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);
+
if (was_readonly)
TREE_READONLY (decl) = 1;
@@ -9335,10 +9444,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;
@@ -9357,7 +9467,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);
@@ -9381,7 +9491,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;
@@ -9523,6 +9633,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
@@ -9674,6 +9786,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:
@@ -14513,8 +14513,6 @@ 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))
- cp_finish_decomp (range_decl, decomp);
warn_for_range_copy (range_decl, deref_begin);
@@ -16429,13 +16427,12 @@ 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);
}
}
else if (decl != error_mark_node)
@@ -44810,8 +44807,6 @@ 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))
- cp_finish_decomp (decl, decomp);
}
/* Return true if next tokens contain a standard attribute that contains
@@ -18633,7 +18633,6 @@ tsubst_stmt (tree t, tree args, tsubst_f
{
bool const_init = false;
cp_decomp decomp_d, *decomp = NULL;
- tree ndecl = error_mark_node;
tree asmspec_tree = NULL_TREE;
maybe_push_decl (decl);
@@ -18646,8 +18645,10 @@ tsubst_stmt (tree t, tree args, tsubst_f
&& TREE_TYPE (pattern_decl) != error_mark_node)
{
decomp = &decomp_d;
- ndecl = tsubst_decomp_names (decl, pattern_decl, args,
- complain, in_decl, decomp);
+ if (tsubst_decomp_names (decl, pattern_decl, args,
+ complain, in_decl, decomp)
+ == error_mark_node)
+ decomp = NULL;
}
init = tsubst_init (init, decl, args, complain, in_decl);
@@ -18674,9 +18675,6 @@ tsubst_stmt (tree t, tree args, tsubst_f
cp_finish_decl (decl, init, const_init, asmspec_tree, 0,
decomp);
-
- if (ndecl != error_mark_node)
- cp_finish_decomp (ndecl, decomp);
}
}
}
@@ -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);
+}
@@ -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);
+}