On Tue, Sep 10, 2024 at 08:29:58PM +0200, Jakub Jelinek wrote:
> Hi!
> 
> The following patch on top of the
> https://gcc.gnu.org/pipermail/gcc-patches/2024-September/662507.html
> patch adds CWG 2867 support for namespace locals.
> 
> Those vars are just pushed into {static,tls}_aggregates chain, then
> pruned from those lists, separated by priority and finally emitted into
> the corresponding dynamic initialization functions.
> The patch adds two flags used on the TREE_LIST nodes in those lists,
> one marks the structured binding base variable and/or associated ref
> extended temps, another marks the vars initialized using get methods.
> The flags are preserved across the pruning, for splitting into by priority
> all associated decls of a structured binding using tuple* are forced
> into the same priority as the first one, and finally when actually emitting
> code, CLEANUP_POINT_EXPRs are disabled in the base initializer(s) and
> code from the bases and non-bases together is wrapped into a single
> CLEANUP_POINT_EXPR.
> 
> Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk?
> 
> Note, I haven't touched the module handling; from what I can see,
> prune_vars_needing_no_initialization is destructive to the
> {static,tls}_aggregates lists (keeps the list NULL at the end or if there
> are errors or it contains some DECL_EXTERNAL decls, keeps in there just
> those, not the actual vars that need dynamic initialization) and
> the module writing is done only afterwards, so I think it could work
> reasonably only if header_module_p ().  Can namespace scope structured
> bindings appear in header_module_p () or !header_module_p () modules?
> How would a testcase using them look like?  Especially when structured
> bindings can't be extern nor templates nor inline there can be just one
> definition, so the module would need to be included in a single file, no?

In the header_module_p case, it is valid to have internal linkage
definitions (e.g. in an anonymous namespace), but in that case the
{static,tls}_aggregates lists should still be in place to be streamed
and everything should work as "normal".

(Note that it is 'valid' but not actually supported yet, I have a patch
series in progress to fix up all the various linkage issues.)

In the !header_module_p case, the modules streaming code doesn't use
those lists at all; namespace-scope definitions are attached to the
module TU directly and any initialization/destruction code is emitted
there.  Definitions would only be streamed if the variables are usable
in constant expressions.

So I don't think there's anything to do for modules here.

Yours,
Nathaniel

> In any case, the patch shouldn't make the modules case any worse, it
> just adds TREE_LIST flags which will not be streamed for modules and so
> if one can use structured bindings in modules, possibly CWG 2867 would be
> not fixed for those but nothing worse than that.
> 
> 2024-09-10  Jakub Jelinek  <ja...@redhat.com>
> 
>       PR c++/115769
> gcc/cp/
>       * cp-tree.h (STATIC_INIT_DECOMP_BASE_P): Define.
>       (STATIC_INIT_DECOMP_NONBASE_P): Define.
>       * decl.cc (cp_finish_decl): Mark nodes in {static,tls}_aggregates
>       with 
>       * decl2.cc (decomp_handle_one_var, decomp_finalize_var_list): New
>       functions.
>       (emit_partial_init_fini_fn): Use them.
>       (prune_vars_needing_no_initialization): Clear
>       STATIC_INIT_DECOMP_*BASE_P flags if needed.
>       (partition_vars_for_init_fini): Use same priority for
>       consecutive STATIC_INIT_DECOMP_*BASE_P vars and propagate
>       those flags to new TREE_LISTs when possible.  Formatting fix.
>       (handle_tls_init): Use decomp_handle_one_var and
>       decomp_finalize_var_list functions.
> gcc/testsuite/
>       * g++.dg/DRs/dr2867-5.C: New test.
>       * g++.dg/DRs/dr2867-6.C: New test.
>       * g++.dg/DRs/dr2867-7.C: New test.
>       * g++.dg/DRs/dr2867-8.C: New test.
> 
> --- gcc/cp/cp-tree.h.jj       2024-09-07 09:31:20.601484156 +0200
> +++ gcc/cp/cp-tree.h  2024-09-09 15:53:44.924112247 +0200
> @@ -470,6 +470,7 @@ extern GTY(()) tree cp_global_trees[CPTI
>        BASELINK_FUNCTIONS_MAYBE_INCOMPLETE_P (in BASELINK)
>        BIND_EXPR_VEC_DTOR (in BIND_EXPR)
>        ATOMIC_CONSTR_EXPR_FROM_CONCEPT_P (in ATOMIC_CONSTR)
> +      STATIC_INIT_DECOMP_BASE_P (in the TREE_LIST for 
> {static,tls}_aggregates)
>     2: IDENTIFIER_KIND_BIT_2 (in IDENTIFIER_NODE)
>        ICS_THIS_FLAG (in _CONV)
>        DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (in VAR_DECL)
> @@ -489,6 +490,8 @@ extern GTY(()) tree cp_global_trees[CPTI
>        IMPLICIT_CONV_EXPR_BRACED_INIT (in IMPLICIT_CONV_EXPR)
>        PACK_EXPANSION_AUTO_P (in *_PACK_EXPANSION)
>        contract_semantic (in ASSERTION_, PRECONDITION_, POSTCONDITION_STMT)
> +      STATIC_INIT_DECOMP_NONBASE_P (in the TREE_LIST
> +                                 for {static,tls}_aggregates)
>     3: IMPLICIT_RVALUE_P (in NON_LVALUE_EXPR or STATIC_CAST_EXPR)
>        ICS_BAD_FLAG (in _CONV)
>        FN_TRY_BLOCK_P (in TRY_BLOCK)
> @@ -5947,6 +5950,21 @@ extern bool defer_mangling_aliases;
>  
>  extern bool flag_noexcept_type;
>  
> +/* True if this TREE_LIST in {static,tls}_aggregates is a for dynamic
> +   initialization of namespace scope structured binding base or related
> +   extended ref init temps.  Temporaries from the initialization of
> +   STATIC_INIT_DECOMP_BASE_P dynamic initializers should be destroyed only
> +   after the last STATIC_INIT_DECOMP_NONBASE_P dynamic initializer following
> +   it.  */
> +#define STATIC_INIT_DECOMP_BASE_P(NODE) \
> +  TREE_LANG_FLAG_1 (TREE_LIST_CHECK (NODE))
> +
> +/* True if this TREE_LIST in {static,tls}_aggregates is a for dynamic
> +   initialization of namespace scope structured binding non-base
> +   variable using get.  */
> +#define STATIC_INIT_DECOMP_NONBASE_P(NODE) \
> +  TREE_LANG_FLAG_2 (TREE_LIST_CHECK (NODE))
> +
>  /* A list of namespace-scope objects which have constructors or
>     destructors which reside in the global scope.  The decl is stored
>     in the TREE_VALUE slot and the initializer is stored in the
> --- gcc/cp/decl.cc.jj 2024-09-09 11:50:07.146394047 +0200
> +++ gcc/cp/decl.cc    2024-09-09 17:16:26.459094150 +0200
> @@ -8485,6 +8485,7 @@ cp_finish_decl (tree decl, tree init, bo
>    bool var_definition_p = false;
>    tree auto_node;
>    auto_vec<tree> extra_cleanups;
> +  tree aggregates1 = NULL_TREE;
>    struct decomp_cleanup {
>      tree decl;
>      cp_decomp *&decomp;
> @@ -8872,7 +8873,16 @@ cp_finish_decl (tree decl, tree init, bo
>       }
>  
>        if (decomp)
> -     cp_maybe_mangle_decomp (decl, decomp);
> +     {
> +       cp_maybe_mangle_decomp (decl, decomp);
> +       if (TREE_STATIC (decl) && !DECL_FUNCTION_SCOPE_P (decl))
> +         {
> +           if (CP_DECL_THREAD_LOCAL_P (decl))
> +             aggregates1 = tls_aggregates;
> +           else
> +             aggregates1 = static_aggregates;
> +         }
> +     }
>  
>        /* If this is a local variable that will need a mangled name,
>        register it now.  We must do this before processing the
> @@ -9210,6 +9220,32 @@ cp_finish_decl (tree decl, tree init, bo
>    if (decomp_init)
>      add_stmt (decomp_init);
>  
> +  if (decomp
> +      && var_definition_p
> +      && TREE_STATIC (decl)
> +      && !DECL_FUNCTION_SCOPE_P (decl))
> +    {
> +      tree &aggregates3 = (CP_DECL_THREAD_LOCAL_P (decl)
> +                        ? tls_aggregates : static_aggregates);
> +      tree aggregates2 = aggregates3;
> +      if (aggregates2 != aggregates1)
> +     {
> +       cp_finish_decomp (decl, decomp);
> +       decomp = NULL;
> +       if (aggregates3 != aggregates2)
> +         {
> +           /* If there are dynamic initializers for the structured
> +              binding base or associated extended ref temps and also
> +              dynamic initializers for the structured binding non-base
> +              vars, mark them.  */
> +           for (tree t = aggregates3; t != aggregates2; t = TREE_CHAIN (t))
> +             STATIC_INIT_DECOMP_NONBASE_P (t) = 1;
> +           for (tree t = aggregates2; t != aggregates1; t = TREE_CHAIN (t))
> +             STATIC_INIT_DECOMP_BASE_P (t) = 1;
> +         }
> +     }
> +    }
> +
>    if (was_readonly)
>      TREE_READONLY (decl) = 1;
>  
> --- gcc/cp/decl2.cc.jj        2024-09-06 13:43:37.759302079 +0200
> +++ gcc/cp/decl2.cc   2024-09-10 13:04:05.475783972 +0200
> @@ -4424,6 +4424,55 @@ one_static_initialization_or_destruction
>    DECL_STATIC_FUNCTION_P (current_function_decl) = 0;
>  }
>  
> +/* Helper function for emit_partial_init_fini_fn and handle_tls_init.
> +   For structured bindings, disable stmts_are_full_exprs_p ()
> +   on STATIC_INIT_DECOMP_BASE_P nodes, reenable it on the
> +   first STATIC_INIT_DECOMP_NONBASE_P node and emit all the
> +   STATIC_INIT_DECOMP_BASE_P and STATIC_INIT_DECOMP_NONBASE_P
> +   consecutive nodes in a single STATEMENT_LIST wrapped with
> +   CLEANUP_POINT_EXPR.  */
> +
> +static inline tree
> +decomp_handle_one_var (tree node, tree sl, bool *saw_nonbase,
> +                    int save_stmts_are_full_exprs_p)
> +{
> +  if (sl && !*saw_nonbase && STATIC_INIT_DECOMP_NONBASE_P (node))
> +    {
> +      *saw_nonbase = true;
> +      current_stmt_tree ()->stmts_are_full_exprs_p
> +     = save_stmts_are_full_exprs_p;
> +    }
> +  else if (sl && *saw_nonbase && !STATIC_INIT_DECOMP_NONBASE_P (node))
> +    {
> +      sl = pop_stmt_list (sl);
> +      sl = maybe_cleanup_point_expr_void (sl);
> +      add_stmt (sl);
> +      sl = NULL_TREE;
> +    }
> +  if (sl == NULL_TREE && STATIC_INIT_DECOMP_BASE_P (node))
> +    {
> +      sl = push_stmt_list ();
> +      *saw_nonbase = false;
> +      current_stmt_tree ()->stmts_are_full_exprs_p = 0;
> +    }
> +  return sl;
> +}
> +
> +/* Similarly helper called when the whole var list is processed.  */
> +
> +static inline void
> +decomp_finalize_var_list (tree sl, int save_stmts_are_full_exprs_p)
> +{
> +  if (sl)
> +    {
> +      current_stmt_tree ()->stmts_are_full_exprs_p
> +     = save_stmts_are_full_exprs_p;
> +      sl = pop_stmt_list (sl);
> +      sl = maybe_cleanup_point_expr_void (sl);
> +      add_stmt (sl);
> +    }
> +}
> +
>  /* Generate code to do the initialization or destruction of the decls in 
> VARS,
>     a TREE_LIST of VAR_DECL with static storage duration.
>     Whether initialization or destruction is performed is specified by INITP. 
>  */
> @@ -4453,12 +4502,17 @@ emit_partial_init_fini_fn (bool initp, u
>        finish_if_stmt_cond (target_dev_p, nonhost_if_stmt);
>      }
>  
> +  tree sl = NULL_TREE;
> +  int save_stmts_are_full_exprs_p = stmts_are_full_exprs_p ();
> +  bool saw_nonbase = false;
>    for (tree node = vars; node; node = TREE_CHAIN (node))
>      {
>        tree decl = TREE_VALUE (node);
>        tree init = TREE_PURPOSE (node);
> -     /* We will emit 'init' twice, and it is modified in-place during
> -        gimplification.  Make a copy here.  */
> +      sl = decomp_handle_one_var (node, sl, &saw_nonbase,
> +                               save_stmts_are_full_exprs_p);
> +      /* We will emit 'init' twice, and it is modified in-place during
> +      gimplification.  Make a copy here.  */
>        if (omp_target)
>       {
>         /* We've already emitted INIT in the host version of the ctor/dtor
> @@ -4482,6 +4536,7 @@ emit_partial_init_fini_fn (bool initp, u
>        /* Do one initialization or destruction.  */
>        one_static_initialization_or_destruction (initp, decl, init);
>      }
> +  decomp_finalize_var_list (sl, save_stmts_are_full_exprs_p);
>  
>    if (omp_target)
>      {
> @@ -4510,6 +4565,7 @@ prune_vars_needing_no_initialization (tr
>  {
>    tree *var = vars;
>    tree result = NULL_TREE;
> +  bool clear_nonbase = false;
>  
>    while (*var)
>      {
> @@ -4517,6 +4573,20 @@ prune_vars_needing_no_initialization (tr
>        tree decl = TREE_VALUE (t);
>        tree init = TREE_PURPOSE (t);
>  
> +      if (STATIC_INIT_DECOMP_BASE_P (t)
> +       && result != NULL_TREE
> +       && STATIC_INIT_DECOMP_NONBASE_P (result))
> +     clear_nonbase = true;
> +      else if (clear_nonbase && !STATIC_INIT_DECOMP_BASE_P (t))
> +     {
> +       clear_nonbase = false;
> +       for (tree r = result; r; r = TREE_CHAIN (r))
> +         if (STATIC_INIT_DECOMP_NONBASE_P (r))
> +           STATIC_INIT_DECOMP_NONBASE_P (r) = 0;
> +         else
> +           break;
> +     }
> +
>        /* Deal gracefully with error.  */
>        if (error_operand_p (decl))
>       {
> @@ -4544,6 +4614,28 @@ prune_vars_needing_no_initialization (tr
>         continue;
>       }
>  
> +      clear_nonbase = false;
> +      /* Ensure that in the returned result chain if the
> +      STATIC_INIT_DECOMP_*BASE_P flags are set, there is always
> +      one or more STATIC_INIT_DECOMP_BASE_P TREE_LIST followed by
> +      one or more STATIC_INIT_DECOMP_NONBASE_P.  */
> +      if (STATIC_INIT_DECOMP_BASE_P (t)
> +       && !(result != NULL_TREE
> +            && (STATIC_INIT_DECOMP_BASE_P (result)
> +                || STATIC_INIT_DECOMP_NONBASE_P (result))))
> +     STATIC_INIT_DECOMP_BASE_P (t) = 0;
> +      else if (!STATIC_INIT_DECOMP_BASE_P (t)
> +            && !STATIC_INIT_DECOMP_NONBASE_P (t)
> +            && result != NULL_TREE
> +            && STATIC_INIT_DECOMP_NONBASE_P (result))
> +     {
> +       for (tree r = result; r; r = TREE_CHAIN (r))
> +         if (STATIC_INIT_DECOMP_NONBASE_P (r))
> +           STATIC_INIT_DECOMP_NONBASE_P (r) = 0;
> +         else
> +           break;
> +     }
> +
>        /* This variable is going to need initialization and/or
>        finalization, so we add it to the list.  */
>        *var = TREE_CHAIN (t);
> @@ -4560,12 +4652,19 @@ prune_vars_needing_no_initialization (tr
>  void
>  partition_vars_for_init_fini (tree var_list, priority_map_t *(&parts)[4])
>  {
> +  unsigned priority = 0;
> +  unsigned decomp_state = 0;
>    for (auto node = var_list; node; node = TREE_CHAIN (node))
>      {
>        tree decl = TREE_VALUE (node);
>        tree init = TREE_PURPOSE (node);
>        bool has_cleanup = !TYPE_HAS_TRIVIAL_DESTRUCTOR (TREE_TYPE (decl));
> -      unsigned priority = DECL_EFFECTIVE_INIT_PRIORITY (decl);
> +      if (decomp_state == 1 && STATIC_INIT_DECOMP_NONBASE_P (node))
> +     decomp_state = 2;
> +      else if (decomp_state == 2 && !STATIC_INIT_DECOMP_NONBASE_P (node))
> +     decomp_state = 0;
> +      if (!decomp_state)
> +     priority = DECL_EFFECTIVE_INIT_PRIORITY (decl);
>  
>        if (init || (flag_use_cxa_atexit && has_cleanup))
>       {
> @@ -4574,6 +4673,34 @@ partition_vars_for_init_fini (tree var_l
>           parts[true] = priority_map_t::create_ggc ();
>         auto &slot = parts[true]->get_or_insert (priority);
>         slot = tree_cons (init, decl, slot);
> +       if (init
> +           && STATIC_INIT_DECOMP_BASE_P (node)
> +           && decomp_state == 0)
> +         {
> +           /* If one or more STATIC_INIT_DECOMP_BASE_P with at least
> +              one init is followed by at least one
> +              STATIC_INIT_DECOMP_NONBASE_P with init, mark it in the
> +              resulting chain as well.  */
> +           for (tree n = TREE_CHAIN (node); n; n = TREE_CHAIN (n))
> +             if (STATIC_INIT_DECOMP_BASE_P (n))
> +               continue;
> +             else if (STATIC_INIT_DECOMP_NONBASE_P (n))
> +               {
> +                 if (TREE_PURPOSE (n))
> +                   {
> +                     decomp_state = 1;
> +                     break;
> +                   }
> +                 else
> +                   continue;
> +               }
> +             else
> +               break;
> +         }
> +       if (init && decomp_state == 1)
> +         STATIC_INIT_DECOMP_BASE_P (slot) = 1;
> +       else if (decomp_state == 2)
> +         STATIC_INIT_DECOMP_NONBASE_P (slot) = 1;
>       }
>  
>        if (!flag_use_cxa_atexit && has_cleanup)
> @@ -4586,7 +4713,7 @@ partition_vars_for_init_fini (tree var_l
>       }
>  
>        if (flag_openmp
> -        && lookup_attribute ("omp declare target", DECL_ATTRIBUTES (decl)))
> +       && lookup_attribute ("omp declare target", DECL_ATTRIBUTES (decl)))
>       {
>         priority_map_t **omp_parts = parts + 2;
>  
> @@ -4597,6 +4724,10 @@ partition_vars_for_init_fini (tree var_l
>               omp_parts[true] = priority_map_t::create_ggc ();
>             auto &slot = omp_parts[true]->get_or_insert (priority);
>             slot = tree_cons (init, decl, slot);
> +           if (init && decomp_state == 1)
> +             STATIC_INIT_DECOMP_BASE_P (slot) = 1;
> +           else if (decomp_state == 2)
> +             STATIC_INIT_DECOMP_NONBASE_P (slot) = 1;
>           }
>  
>         if (!flag_use_cxa_atexit && has_cleanup)
> @@ -4983,10 +5114,15 @@ handle_tls_init (void)
>    finish_expr_stmt (cp_build_modify_expr (loc, guard, NOP_EXPR,
>                                         boolean_true_node,
>                                         tf_warning_or_error));
> +  tree sl = NULL_TREE;
> +  int save_stmts_are_full_exprs_p = stmts_are_full_exprs_p ();
> +  bool saw_nonbase = false;
>    for (; vars; vars = TREE_CHAIN (vars))
>      {
>        tree var = TREE_VALUE (vars);
>        tree init = TREE_PURPOSE (vars);
> +      sl = decomp_handle_one_var (vars, sl, &saw_nonbase,
> +                               save_stmts_are_full_exprs_p);
>        one_static_initialization_or_destruction (/*initp=*/true, var, init);
>  
>        /* Output init aliases even with -fno-extern-tls-init.  */
> @@ -5001,6 +5137,7 @@ handle_tls_init (void)
>         gcc_assert (alias != NULL);
>       }
>      }
> +  decomp_finalize_var_list (sl, save_stmts_are_full_exprs_p);
>  
>    finish_then_clause (if_stmt);
>    finish_if_stmt (if_stmt);
> --- gcc/testsuite/g++.dg/DRs/dr2867-5.C.jj    2024-09-09 14:09:22.181185411 
> +0200
> +++ gcc/testsuite/g++.dg/DRs/dr2867-5.C       2024-09-10 10:44:40.859421538 
> +0200
> @@ -0,0 +1,92 @@
> +// CWG2867 - Order of initialization for structured bindings.
> +// { dg-do run { target c++11 } }
> +// { dg-options "" }
> +
> +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
> +
> +namespace std {
> +  template<typename T> struct tuple_size;
> +  template<int, typename> struct tuple_element;
> +}
> +
> +int a, c, d, i;
> +
> +struct A {
> +  A () { assert (c == 3); ++c; }
> +  ~A () { ++a; }
> +  template <int I> int &get () const { assert (c == 5 + I); ++c; return i; }
> +};
> +
> +template <> struct std::tuple_size <A> { static const int value = 4; };
> +template <int I> struct std::tuple_element <I, A> { using type = int; };
> +template <> struct std::tuple_size <const A> { static const int value = 4; };
> +template <int I> struct std::tuple_element <I, const A> { using type = int; 
> };
> +
> +struct B {
> +  B () { assert (c >= 1 && c <= 2); ++c; }
> +  ~B () { assert (c >= 9 && c <= 10); ++c; }
> +};
> +
> +struct C {
> +  constexpr C () {}
> +  constexpr C (const C &) {}
> +  template <int I> int &get () const { assert (d == 1 + I); ++d; return i; }
> +};
> +
> +template <> struct std::tuple_size <C> { static const int value = 3; };
> +template <int I> struct std::tuple_element <I, C> { using type = int; };
> +template <> struct std::tuple_size <const C> { static const int value = 3; };
> +template <int I> struct std::tuple_element <I, const C> { using type = int; 
> };
> +
> +A
> +foo (const B &, const B &)
> +{
> +  A a;
> +  assert (c == 4);
> +  ++c;
> +  return a;
> +}
> +
> +constexpr C
> +foo (const C &, const C &)
> +{
> +  return C {};
> +}
> +
> +int
> +bar (int &x, int y)
> +{
> +  x = y;
> +  return y;
> +}
> +
> +int
> +baz (int &x, int y)
> +{
> +  assert (x == y);
> +  return y;
> +}
> +
> +struct E {
> +  ~E () { assert (a == 2); }
> +};
> +
> +E e;
> +int c1 = bar (c, 1);
> +const auto &[x, y, z, w] = foo (B {}, B {}); // { dg-warning "structured 
> bindings only available with" "" { target c++14_down } }
> +int c2 = baz (c, 11);
> +int d1 = bar (d, 1);
> +const auto &[s, t, u] = foo (C {}, C {});    // { dg-warning "structured 
> bindings only available with" "" { target c++14_down } }
> +int d2 = baz (d, 4);
> +int c3 = bar (c, 1);
> +auto [x2, y2, z2, w2] = foo (B {}, B {});    // { dg-warning "structured 
> bindings only available with" "" { target c++14_down } }
> +int c4 = baz (c, 11);
> +int d3 = bar (d, 1);
> +auto [s2, t2, u2] = foo (C {}, C {});                // { dg-warning 
> "structured bindings only available with" "" { target c++14_down } }
> +int d4 = baz (d, 4);
> +
> +int
> +main ()
> +{
> +  assert (a == 0);
> +}
> --- gcc/testsuite/g++.dg/DRs/dr2867-6.C.jj    2024-09-09 14:19:56.455059937 
> +0200
> +++ gcc/testsuite/g++.dg/DRs/dr2867-6.C       2024-09-09 14:56:22.572568526 
> +0200
> @@ -0,0 +1,83 @@
> +// CWG2867 - Order of initialization for structured bindings.
> +// { dg-do run { target c++11 } }
> +// { dg-options "" }
> +
> +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
> +
> +namespace std {
> +  template<typename T> struct tuple_size;
> +  template<int, typename> struct tuple_element;
> +}
> +
> +int a, c;
> +
> +struct C {
> +  C () { assert (c >= 5 && c <= 17 && (c - 5) % 4 == 0); ++c; }
> +  ~C () { assert (c >= 8 && c <= 20 && c % 4 == 0); ++c; }
> +};
> +
> +struct D {
> +  D () { assert (c >= 7 && c <= 19 && (c - 7) % 4 == 0); ++c; }
> +  ~D () { assert (a % 5 != 4); ++a; }
> +};
> +
> +struct A {
> +  A () { assert (c == 3); ++c; }
> +  ~A () { assert (a % 5 == 4); ++a; }
> +  template <int I> D get (const C & = C{}) const { assert (c == 6 + 4 * I); 
> ++c; return D {}; }
> +};
> +
> +template <> struct std::tuple_size <A> { static const int value = 4; };
> +template <int I> struct std::tuple_element <I, A> { using type = D; };
> +template <> struct std::tuple_size <const A> { static const int value = 4; };
> +template <int I> struct std::tuple_element <I, const A> { using type = D; };
> +
> +struct B {
> +  B () { assert (c >= 1 && c <= 2); ++c; }
> +  ~B () { assert (c >= 21 && c <= 22); ++c; }
> +};
> +
> +A
> +foo (const B &, const B &)
> +{
> +  A a;
> +  assert (c == 4);
> +  ++c;
> +  return a;
> +}
> +
> +int
> +bar (int &x, int y)
> +{
> +  x = y;
> +  return y;
> +}
> +
> +int
> +baz (int &x, int y)
> +{
> +  assert (x == y);
> +  return y;
> +}
> +
> +struct E {
> +  ~E () { assert (a == 5); }
> +};
> +
> +E e;
> +int c1 = bar (c, 1);
> +// First B::B () is invoked twice, then foo called, which invokes A::A ().
> +// e is reference bound to the A::A () constructed temporary.
> +// Then 4 times (in increasing I):
> +//   C::C () is invoked, get is called, D::D () is invoked, C::~C () is
> +//   invoked.
> +// After that B::~B () is invoked twice.
> +// At exit time D::~D () is invoked 4 times, then A::~A ().
> +const auto &[x, y, z, w] = foo (B {}, B {}); // { dg-warning "structured 
> bindings only available with" "" { target c++14_down } }
> +int c2 = baz (c, 23);
> +
> +int
> +main ()
> +{
> +  assert (a == 0);
> +}
> --- gcc/testsuite/g++.dg/DRs/dr2867-7.C.jj    2024-09-10 12:08:07.770933520 
> +0200
> +++ gcc/testsuite/g++.dg/DRs/dr2867-7.C       2024-09-10 12:19:48.730462845 
> +0200
> @@ -0,0 +1,98 @@
> +// CWG2867 - Order of initialization for structured bindings.
> +// { dg-do run { target c++11 } }
> +// { dg-options "" }
> +// { dg-add-options tls }
> +// { dg-require-effective-target tls_runtime }
> +
> +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
> +
> +namespace std {
> +  template<typename T> struct tuple_size;
> +  template<int, typename> struct tuple_element;
> +}
> +
> +int a, c, d, i;
> +
> +struct A {
> +  A () { assert (c == 3); ++c; }
> +  ~A () { ++a; }
> +  template <int I> int &get () const { assert (c == 5 + I); ++c; return i; }
> +};
> +
> +template <> struct std::tuple_size <A> { static const int value = 4; };
> +template <int I> struct std::tuple_element <I, A> { using type = int; };
> +template <> struct std::tuple_size <const A> { static const int value = 4; };
> +template <int I> struct std::tuple_element <I, const A> { using type = int; 
> };
> +
> +struct B {
> +  B () { assert (c >= 1 && c <= 2); ++c; }
> +  ~B () { assert (c >= 9 && c <= 10); ++c; }
> +};
> +
> +struct C {
> +  constexpr C () {}
> +  constexpr C (const C &) {}
> +  template <int I> int &get () const { assert (d == 1 + I); ++d; return i; }
> +};
> +
> +template <> struct std::tuple_size <C> { static const int value = 3; };
> +template <int I> struct std::tuple_element <I, C> { using type = int; };
> +template <> struct std::tuple_size <const C> { static const int value = 3; };
> +template <int I> struct std::tuple_element <I, const C> { using type = int; 
> };
> +
> +A
> +foo (const B &, const B &)
> +{
> +  A a;
> +  assert (c == 4);
> +  ++c;
> +  return a;
> +}
> +
> +constexpr C
> +foo (const C &, const C &)
> +{
> +  return C {};
> +}
> +
> +int
> +bar (int &x, int y)
> +{
> +  x = y;
> +  return y;
> +}
> +
> +int
> +baz (int &x, int y)
> +{
> +  assert (x == y);
> +  return y;
> +}
> +
> +struct E {
> +  ~E () { assert (a == 2); }
> +};
> +
> +thread_local E e;
> +thread_local int c1 = bar (c, 1);
> +thread_local const auto &[x, y, z, w] = foo (B {}, B {});    // { dg-warning 
> "structured bindings only available with" "" { target c++14_down } }
> +thread_local int c2 = baz (c, 11);                           // { dg-warning 
> "structured binding declaration can be 'thread_local' only in" "" { target 
> c++17_down } .-1 }
> +thread_local int d1 = bar (d, 1);
> +thread_local const auto &[s, t, u] = foo (C {}, C {});               // { 
> dg-warning "structured bindings only available with" "" { target c++14_down } 
> }
> +thread_local int d2 = baz (d, 4);                            // { dg-warning 
> "structured binding declaration can be 'thread_local' only in" "" { target 
> c++17_down } .-1 }
> +thread_local int c3 = bar (c, 1);
> +thread_local auto [x2, y2, z2, w2] = foo (B {}, B {});               // { 
> dg-warning "structured bindings only available with" "" { target c++14_down } 
> }
> +thread_local int c4 = baz (c, 11);                           // { dg-warning 
> "structured binding declaration can be 'thread_local' only in" "" { target 
> c++17_down } .-1 }
> +thread_local int d3 = bar (d, 1);
> +thread_local auto [s2, t2, u2] = foo (C {}, C {});           // { dg-warning 
> "structured bindings only available with" "" { target c++14_down } }
> +thread_local int d4 = baz (d, 4);                            // { dg-warning 
> "structured binding declaration can be 'thread_local' only in" "" { target 
> c++17_down } .-1 }
> +
> +int
> +main ()
> +{
> +  volatile int u = c1 + x + y + z + w + c2;
> +  u += d1 + s + t + u + d2;
> +  u += c3 + x2 + y2 + z2 + w2 + c4;
> +  u += d3 + s2 + t2 + u2 + d4;
> +  assert (a == 0);
> +}
> --- gcc/testsuite/g++.dg/DRs/dr2867-8.C.jj    2024-09-10 12:09:28.773839087 
> +0200
> +++ gcc/testsuite/g++.dg/DRs/dr2867-8.C       2024-09-10 12:34:06.556878510 
> +0200
> @@ -0,0 +1,86 @@
> +// CWG2867 - Order of initialization for structured bindings.
> +// { dg-do run { target c++11 } }
> +// { dg-options "" }
> +// { dg-add-options tls }
> +// { dg-require-effective-target tls_runtime }
> +
> +#define assert(X) do { if (!(X)) __builtin_abort(); } while (0)
> +
> +namespace std {
> +  template<typename T> struct tuple_size;
> +  template<int, typename> struct tuple_element;
> +}
> +
> +int a, c;
> +
> +struct C {
> +  C () { assert (c >= 5 && c <= 17 && (c - 5) % 4 == 0); ++c; }
> +  ~C () { assert (c >= 8 && c <= 20 && c % 4 == 0); ++c; }
> +};
> +
> +struct D {
> +  D () { assert (c >= 7 && c <= 19 && (c - 7) % 4 == 0); ++c; }
> +  ~D () { assert (a % 5 != 4); ++a; }
> +};
> +
> +struct A {
> +  A () { assert (c == 3); ++c; }
> +  ~A () { assert (a % 5 == 4); ++a; }
> +  template <int I> D get (const C & = C{}) const { assert (c == 6 + 4 * I); 
> ++c; return D {}; }
> +};
> +
> +template <> struct std::tuple_size <A> { static const int value = 4; };
> +template <int I> struct std::tuple_element <I, A> { using type = D; };
> +template <> struct std::tuple_size <const A> { static const int value = 4; };
> +template <int I> struct std::tuple_element <I, const A> { using type = D; };
> +
> +struct B {
> +  B () { assert (c >= 1 && c <= 2); ++c; }
> +  ~B () { assert (c >= 21 && c <= 22); ++c; }
> +};
> +
> +A
> +foo (const B &, const B &)
> +{
> +  A a;
> +  assert (c == 4);
> +  ++c;
> +  return a;
> +}
> +
> +int
> +bar (int &x, int y)
> +{
> +  x = y;
> +  return y;
> +}
> +
> +int
> +baz (int &x, int y)
> +{
> +  assert (x == y);
> +  return y;
> +}
> +
> +struct E {
> +  ~E () { assert (a == 5); }
> +};
> +
> +thread_local E e;
> +thread_local int c1 = bar (c, 1);
> +// First B::B () is invoked twice, then foo called, which invokes A::A ().
> +// e is reference bound to the A::A () constructed temporary.
> +// Then 4 times (in increasing I):
> +//   C::C () is invoked, get is called, D::D () is invoked, C::~C () is
> +//   invoked.
> +// After that B::~B () is invoked twice.
> +// At exit time D::~D () is invoked 4 times, then A::~A ().
> +thread_local const auto &[x, y, z, w] = foo (B {}, B {});    // { dg-warning 
> "structured bindings only available with" "" { target c++14_down } }
> +thread_local int c2 = baz (c, 23);                           // { dg-warning 
> "structured binding declaration can be 'thread_local' only in" "" { target 
> c++17_down } .-1 }
> +
> +int
> +main ()
> +{
> +  volatile int u = c1 + c2;
> +  assert (a == 0);
> +}
> 
>       Jakub
> 

Reply via email to