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