https://gcc.gnu.org/g:84ec92254bda8e7675dd2b3bfd5c76255568e72e
commit r17-3889-g84ec92254bda8e7675dd2b3bfd5c76255568e72e Author: Jakub Jelinek <[email protected]> Date: Thu Sep 3 09:45:58 2026 +0200 c++: Fix up constexpr structured bindings used as condition [PR127109] 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. This patch stops using TARGET_EXPR for this and instead uses get_temp_regvar with pushdecl, instead of DECL_DECOMP_BASE recorded as NON_LVALUE_EXPR around that temp var to differentiate it from structured binding vars other than the base where the VAR_DECL stands for the base. 2026-09-03 Jakub Jelinek <[email protected]> PR c++/127109 * decl.cc (cp_finish_decomp): Add [dcl.struct.bind]/7 reference to comment. Use get_temp_regvar and pushdecl instead of get_internal_target_expr and add_stmt. Set DECL_DECOMP_BASE to NON_LVALUE_EXPR around the temp regvar. * cp-tree.h (DECL_DECOMP_BASE): Adjust macro comment. * semantics.cc (maybe_convert_cond, switch_finish_cond): Check for NON_LVALUE_EXPR rather than TARGET_EXPR and extract its operand rather than TARGET_EXPR_SLOT. * g++.dg/cpp26/decomp32.C: New test. Reviewed-by: Jason Merrill <[email protected]> Diff: --- gcc/cp/cp-tree.h | 2 +- gcc/cp/decl.cc | 19 ++++++++++++------- gcc/cp/semantics.cc | 12 ++++++------ gcc/testsuite/g++.dg/cpp26/decomp32.C | 33 +++++++++++++++++++++++++++++++++ 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 6b9c8c1ba3f5..369d2a9119d3 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -4845,7 +4845,7 @@ get_vec_init_expr (tree t) /* The underlying artificial VAR_DECL for structured binding. On the artificial base VAR_DECL this can be NULL, or integer_{zero,one}_node for structured binding used in if/while/for resp. switch conditions, - or a TARGET_EXPR with the condition value after cp_finish_decomp in + or a NON_LVALUE_EXPR with the condition value after cp_finish_decomp in those cases. */ #define DECL_DECOMP_BASE(NODE) \ (LANG_DECL_DECOMP_CHECK (NODE)->base) diff --git a/gcc/cp/decl.cc b/gcc/cp/decl.cc index 27183cd98852..6e45321e9710 100644 --- a/gcc/cp/decl.cc +++ b/gcc/cp/decl.cc @@ -11105,7 +11105,10 @@ cp_finish_decomp (tree decl, cp_decomp *decomp, bool test_p) /* For structured bindings used in conditions we need to evaluate the conversion of decl (aka e in the standard) to bool or integral/enumeral type (the latter for switch conditions) - before the get methods. */ + before the get methods, as [dcl.struct.bind]/7 requires that: + "The initialization of e and any conversion of e considered as + a decision variable is sequenced before the initialization of + any r_i." */ tree cond = convert_from_reference (decl); if (integer_onep (DECL_DECOMP_BASE (decl))) /* switch condition. */ @@ -11116,12 +11119,14 @@ cp_finish_decomp (tree decl, cp_decomp *decomp, bool test_p) 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; + cond = get_temp_regvar (TREE_TYPE (cond), cond); + pushdecl (cond); + /* Set DECL_DECOMP_BASE to cond VAR_DECL wrapped in + NON_LVALUE_EXPR, such that it is considered to be + the condition of a structured binding rather than + structured binding's base variable. */ + DECL_DECOMP_BASE (decl) + = build1 (NON_LVALUE_EXPR, TREE_TYPE (cond), cond); } } int save_read = DECL_READ_P (decl); diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc index 469f71dcab6d..bb952e2f62cb 100644 --- a/gcc/cp/semantics.cc +++ b/gcc/cp/semantics.cc @@ -1139,12 +1139,12 @@ maybe_convert_cond (tree cond) /* For structured binding used in condition, the conversion needs to be evaluated before the individual variables are initialized in the std::tuple_{size,element} case. cp_finish_decomp saved the conversion - result in a TARGET_EXPR, pick it up from there. */ + result in a NON_LVALUE_EXPR, pick it up from there. */ if (DECL_DECOMPOSITION_P (cond) && DECL_DECOMP_IS_BASE (cond) && DECL_DECOMP_BASE (cond) - && TREE_CODE (DECL_DECOMP_BASE (cond)) == TARGET_EXPR) - cond = TARGET_EXPR_SLOT (DECL_DECOMP_BASE (cond)); + && TREE_CODE (DECL_DECOMP_BASE (cond)) == NON_LVALUE_EXPR) + cond = TREE_OPERAND (DECL_DECOMP_BASE (cond), 0); if (warn_sequence_point && !processing_template_decl) verify_sequence_points (cond); @@ -1932,12 +1932,12 @@ finish_switch_cond (tree cond, tree switch_stmt) /* For structured binding used in condition, the conversion needs to be evaluated before the individual variables are initialized in the std::tuple_{size,element} case. cp_finish_decomp saved the - conversion result in a TARGET_EXPR, pick it up from there. */ + conversion result in a NON_LVALUE_EXPR, pick it up from there. */ if (DECL_DECOMPOSITION_P (cond) && DECL_DECOMP_IS_BASE (cond) && DECL_DECOMP_BASE (cond) - && TREE_CODE (DECL_DECOMP_BASE (cond)) == TARGET_EXPR) - cond = TARGET_EXPR_SLOT (DECL_DECOMP_BASE (cond)); + && TREE_CODE (DECL_DECOMP_BASE (cond)) == NON_LVALUE_EXPR) + cond = TREE_OPERAND (DECL_DECOMP_BASE (cond), 0); cond = build_expr_type_conversion (WANT_INT | WANT_ENUM, cond, true); if (cond == NULL_TREE) { diff --git a/gcc/testsuite/g++.dg/cpp26/decomp32.C b/gcc/testsuite/g++.dg/cpp26/decomp32.C new file mode 100644 index 000000000000..e8b039675532 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp26/decomp32.C @@ -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);
