Hi!

The following testcase is incorrectly rejected with
error: accessing '<anonymous>' outside its lifetime
where <anonymous> is the TARGET_EXPR_SLOT of get_internal_target_expr
returned TARGET_EXPR created in cp_finish_decomp.
The problem is we create
    <<cleanup_point <<< Unknown tree: expr_stmt
      D.2723.a = v >>>;
    <<< Unknown tree: expr_stmt
      D.2723.b = v * 2 >>>;
    <<< Unknown tree: expr_stmt
      D.2723.c = v != 0 >>>;
    D.2758 = 1;
    TARGET_EXPR <D.2759, A::operator bool (&D.2723)>;
        const type & a;
    <<cleanup_point <<< Unknown tree: expr_stmt
      (void) (a = (const type &) A::get<0> (&D.2723)) >>>>>;
        const type & b;
    <<cleanup_point <<< Unknown tree: expr_stmt
      (void) (b = (const type &) A::get<1> (&D.2723)) >>>>>;>>;
    if (D.2759)
      {
        return <retval> = (int) *a + (int) *b;
      }
where because of CWG2867 the first CLEANUP_POINT_EXPR wraps the whole
structured binding initialization.  The <anonymous> var is D.2759
above, we need to remember the return value from operator bool,
but it is then used in the if condition after the initialization.
The CLEANUP_POINT_EXPR around the whole initialization does destruct_value
the TARGET_EXPR slot though, so the use in if (D.2759) is then during
constant evaluation considered out of lifetime use.

The patch below fixes it by creating a TARGET_EXPR for it with
false initializer instead of the operator bool call (tried void_node
initialization but that didn't work) and in the spot where the TARGET_EXPR
appears above it instead emits a MODIFY_EXPR with TARGET_EXPR on the lhs
and operator bool on the rhs.  And later on cp_finish_decl prepends the
DECL_DECOMP_BASE (the TARGET_EXPR) before the CLEANUP_POINT_EXPR, so we get
-    <<cleanup_point <<< Unknown tree: expr_stmt
+    (void) TARGET_EXPR <D.2759, 0>, <<cleanup_point <<< Unknown tree: expr_stmt
       D.2723.a = v >>>;
     <<< Unknown tree: expr_stmt
       D.2723.b = v * 2 >>>;
     <<< Unknown tree: expr_stmt
       D.2723.c = v != 0 >>>;
     D.2758 = 1;
-    TARGET_EXPR <D.2759, A::operator bool (&D.2723)>;
+    TARGET_EXPR <D.2759, 0> = A::operator bool (&D.2723);
This way the if (D.2759) use is not out of lifetime read anymore.
For -O1 and above this change shouldn't change anything, the useless
D.2759 = 0; store should be optimized out because there is unconditional
D.2759 = A::operator bool (&D.2723); later on (unless the structured binding
initializers can throw).

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

2026-09-01  Jakub Jelinek  <[email protected]>

        PR c++/127109
        * decl.cc (cp_finish_decl): If DECL_DECOMP_BASE is a TARGET_EXPR,
        set decomp_init to COMPOUND_EXPR of that TARGET_EXPR and previous
        decomp_init expression.
        (cp_finish_decomp): Call get_internal_target_expr on zero constant
        off the TREE_TYPE (cond) type rather than cond itself and add
        a MODIFY_EXPR to set the TARGET_EXPR to cond.

        * g++.dg/cpp26/decomp32.C: New test.

--- a/gcc/cp/decl.cc    2026-08-27 17:41:43.923633215 +0200
+++ b/gcc/cp/decl.cc    2026-08-31 11:59:11.560985581 +0200
@@ -10290,6 +10290,11 @@ cp_finish_decl (tree decl, tree init, bo
                    }
                }
              decomp_init = maybe_cleanup_point_expr_void (sl);
+             if (DECL_DECOMP_BASE (decl)
+                 && TREE_CODE (DECL_DECOMP_BASE (decl)) == TARGET_EXPR)
+               decomp_init = build_compound_expr (DECL_SOURCE_LOCATION (decl),
+                                                  DECL_DECOMP_BASE (decl),
+                                                  decomp_init);
              if (cleanup)
                finish_decl_cleanup (decl, cleanup);
            }
@@ -11110,12 +11115,14 @@ cp_finish_decomp (tree decl, cp_decomp *
            cond = contextual_conv_bool (cond, tf_warning_or_error);
          if (cond && !error_operand_p (cond))
            {
-             /* Wrap that value into a TARGET_EXPR, emit it right
-                away and save for later uses in the cp_parse_condition
-                or its instantiation.  */
-             cond = get_internal_target_expr (cond);
-             add_stmt (cond);
-             DECL_DECOMP_BASE (decl) = cond;
+             /* Set a TARGET_EXPR to that value and save the TARGET_EXPR
+                for later uses in the cp_parse_condition or its
+                instantiation.  */
+             tree t
+               = get_internal_target_expr (build_zero_cst (TREE_TYPE (cond)));
+             tree stmt = build2 (MODIFY_EXPR, void_type_node, t, cond);
+             add_stmt (stmt);
+             DECL_DECOMP_BASE (decl) = t;
            }
        }
       int save_read = DECL_READ_P (decl);
--- a/gcc/testsuite/g++.dg/cpp26/decomp32.C     2026-08-31 11:56:36.449983506 
+0200
+++ b/gcc/testsuite/g++.dg/cpp26/decomp32.C     2026-08-31 12:03:31.573636468 
+0200
@@ -0,0 +1,33 @@
+// PR c++/127109
+// { dg-do compile { target c++14 } }
+// { dg-options "" }
+
+namespace std {
+  using size_t = decltype (sizeof 0);
+  template <typename> struct tuple_size;
+  template <size_t, typename> struct tuple_element;
+}
+
+struct A {
+  int a, b;
+  bool c;
+  constexpr explicit operator bool () const { return c; }
+  template <std::size_t I>
+  constexpr const int &get () const { return I == 0 ? a : b; }
+};
+
+template <>
+struct std::tuple_size <A> { static constexpr int value = 2; };
+template <std::size_t I>
+struct std::tuple_element <I, A> { using type = const int; };
+
+constexpr int
+foo (int v)
+{
+  if (auto [a, b] = A { v, v * 2, v != 0 })    // { dg-warning "structured 
bindings in conditions only available with" "" { target c++23_down } }
+    return a + b;
+  return -1;
+}
+
+static_assert (foo (1) == 3);
+static_assert (foo (0) == -1);

        Jakub

Reply via email to