c++, v2: Implement CWG3135 - constexpr structured bindings with prvalues from tuples
Commit Message
On Tue, Apr 07, 2026 at 05:51:53PM +0200, Jakub Jelinek wrote:
> On Tue, Apr 07, 2026 at 11:44:15AM -0400, Jason Merrill wrote:
> > > The pr108503.C testcase was the only one affected in the testsuite.
> >
> > I've gotten a couple of CWG responses now agreeing that it should have been
> > a DR. Do you have an opinion about whether to go ahead with dropping the
> > cxx_dialect check now, or put the patch in as is and drop it for GCC 17? I
> > lean slightly toward making the change now, the effect on any existing C++20
> > code ought to be very small since any use of the binding name is an lvalue
> > either way.
>
> I'll test a version which assumes it is a DR tonight.
Here is so far lightly tested patch, ok for trunk if it passes full
bootstrap/regtest?
2026-04-07 Jakub Jelinek <jakub@redhat.com>
* decl.cc (cp_finish_decomp): Implement CWG3135 - constexpr structured
bindings with prvalues from tuples (as a DR). Don't call
cp_build_reference_type if init is prvalue.
* g++.dg/cpp26/decomp30.C: New test.
* g++.dg/gomp/pr108503.C: Adjust expected diagnostic locations.
Jakub
Comments
On 4/7/26 2:50 PM, Jakub Jelinek wrote:
> On Tue, Apr 07, 2026 at 05:51:53PM +0200, Jakub Jelinek wrote:
>> On Tue, Apr 07, 2026 at 11:44:15AM -0400, Jason Merrill wrote:
>>>> The pr108503.C testcase was the only one affected in the testsuite.
>>>
>>> I've gotten a couple of CWG responses now agreeing that it should have been
>>> a DR. Do you have an opinion about whether to go ahead with dropping the
>>> cxx_dialect check now, or put the patch in as is and drop it for GCC 17? I
>>> lean slightly toward making the change now, the effect on any existing C++20
>>> code ought to be very small since any use of the binding name is an lvalue
>>> either way.
>>
>> I'll test a version which assumes it is a DR tonight.
>
> Here is so far lightly tested patch, ok for trunk if it passes full
> bootstrap/regtest?
OK.
> 2026-04-07 Jakub Jelinek <jakub@redhat.com>
>
> * decl.cc (cp_finish_decomp): Implement CWG3135 - constexpr structured
> bindings with prvalues from tuples (as a DR). Don't call
> cp_build_reference_type if init is prvalue.
>
> * g++.dg/cpp26/decomp30.C: New test.
> * g++.dg/gomp/pr108503.C: Adjust expected diagnostic locations.
>
> --- gcc/cp/decl.cc.jj 2026-04-06 23:08:40.440491516 +0200
> +++ gcc/cp/decl.cc 2026-04-07 20:42:04.966830492 +0200
> @@ -11025,7 +11025,8 @@ cp_finish_decomp (tree decl, cp_decomp *
> maybe_push_decl (t);
> /* Save the decltype away before reference collapse. */
> hash_map_safe_put<hm_ggc> (decomp_type_table, t, eltype);
> - eltype = cp_build_reference_type (eltype, !lvalue_p (init));
> + if (glvalue_p (init))
> + eltype = cp_build_reference_type (eltype, !lvalue_p (init));
> TREE_TYPE (t) = eltype;
> layout_decl (t, 0);
> DECL_HAS_VALUE_EXPR_P (t) = 0;
> @@ -11070,7 +11071,8 @@ cp_finish_decomp (tree decl, cp_decomp *
> }
> /* Save the decltype away before reference collapse. */
> hash_map_safe_put<hm_ggc> (decomp_type_table, v[i], eltype);
> - eltype = cp_build_reference_type (eltype, !lvalue_p (init));
> + if (glvalue_p (init))
> + eltype = cp_build_reference_type (eltype, !lvalue_p (init));
> TREE_TYPE (v[i]) = eltype;
> layout_decl (v[i], 0);
> if (DECL_HAS_VALUE_EXPR_P (v[i]))
> --- gcc/testsuite/g++.dg/cpp26/decomp30.C.jj 2026-04-07 20:41:25.413496275 +0200
> +++ gcc/testsuite/g++.dg/cpp26/decomp30.C 2026-04-07 20:45:04.141841379 +0200
> @@ -0,0 +1,35 @@
> +// CWG3135 - constexpr structured bindings with prvalues from tuples
> +// { dg-do compile { target c++14 } }
> +// { dg-options "" }
> +
> +namespace std {
> + template <typename T> struct tuple_size;
> + template <int, typename> struct tuple_element;
> +}
> +
> +struct A {
> + template <int I> constexpr int get () const { return I + 6; }
> +};
> +
> +template <> struct std::tuple_size <A> { static const int value = 2; };
> +template <int I> struct std::tuple_element <I, A> { using type = int; };
> +template <> struct std::tuple_size <const A> { static const int value = 2; };
> +template <int I> struct std::tuple_element <I, const A> { using type = const int; };
> +
> +constexpr A
> +bar ()
> +{
> + return A {};
> +}
> +
> +template <int N>
> +constexpr int
> +foo ()
> +{
> + constexpr auto [...i] = bar (); // { dg-warning "structured binding packs only available with" "" { target { c++17 && c++23_down } } }
> + // { dg-warning "structured binding declaration can be 'constexpr' only with" "" { target c++23_down } .-1 }
> + // { dg-warning "structured bindings only available with" "" { target c++14_down } .-2 }
> + return i...[0] + i...[1]; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
> +}
> +
> +static_assert (foo <42> () == 13);
> --- gcc/testsuite/g++.dg/gomp/pr108503.C.jj 2026-03-27 10:17:15.840302900 +0100
> +++ gcc/testsuite/g++.dg/gomp/pr108503.C 2026-04-07 20:46:43.518183216 +0200
> @@ -21,7 +21,7 @@ void
> foo (B a)
> {
> #pragma omp for collapse(2)
> - for (auto [i, j, k] : a)
> - for (int l = i; l < j; l += k) // { dg-error "initializer expression refers to iteration variable 'i'" }
> - ; // { dg-error "condition expression refers to iteration variable 'j'" "" { target *-*-* } .-1 }
> -} // { dg-error "increment expression refers to iteration variable 'k'" "" { target *-*-* } .-2 }
> + for (auto [i, j, k] : a) // { dg-error "initializer expression refers to iteration variable 'i'" }
> + for (int l = i; l < j; l += k) // { dg-error "condition expression refers to iteration variable 'j'" }
> + ; // { dg-error "increment expression refers to iteration variable 'k'" "" { target *-*-* } .-2 }
> +}
>
>
> Jakub
>
@@ -11025,7 +11025,8 @@ cp_finish_decomp (tree decl, cp_decomp *
maybe_push_decl (t);
/* Save the decltype away before reference collapse. */
hash_map_safe_put<hm_ggc> (decomp_type_table, t, eltype);
- eltype = cp_build_reference_type (eltype, !lvalue_p (init));
+ if (glvalue_p (init))
+ eltype = cp_build_reference_type (eltype, !lvalue_p (init));
TREE_TYPE (t) = eltype;
layout_decl (t, 0);
DECL_HAS_VALUE_EXPR_P (t) = 0;
@@ -11070,7 +11071,8 @@ cp_finish_decomp (tree decl, cp_decomp *
}
/* Save the decltype away before reference collapse. */
hash_map_safe_put<hm_ggc> (decomp_type_table, v[i], eltype);
- eltype = cp_build_reference_type (eltype, !lvalue_p (init));
+ if (glvalue_p (init))
+ eltype = cp_build_reference_type (eltype, !lvalue_p (init));
TREE_TYPE (v[i]) = eltype;
layout_decl (v[i], 0);
if (DECL_HAS_VALUE_EXPR_P (v[i]))
@@ -0,0 +1,35 @@
+// CWG3135 - constexpr structured bindings with prvalues from tuples
+// { dg-do compile { target c++14 } }
+// { dg-options "" }
+
+namespace std {
+ template <typename T> struct tuple_size;
+ template <int, typename> struct tuple_element;
+}
+
+struct A {
+ template <int I> constexpr int get () const { return I + 6; }
+};
+
+template <> struct std::tuple_size <A> { static const int value = 2; };
+template <int I> struct std::tuple_element <I, A> { using type = int; };
+template <> struct std::tuple_size <const A> { static const int value = 2; };
+template <int I> struct std::tuple_element <I, const A> { using type = const int; };
+
+constexpr A
+bar ()
+{
+ return A {};
+}
+
+template <int N>
+constexpr int
+foo ()
+{
+ constexpr auto [...i] = bar (); // { dg-warning "structured binding packs only available with" "" { target { c++17 && c++23_down } } }
+ // { dg-warning "structured binding declaration can be 'constexpr' only with" "" { target c++23_down } .-1 }
+ // { dg-warning "structured bindings only available with" "" { target c++14_down } .-2 }
+ return i...[0] + i...[1]; // { dg-warning "pack indexing only available with" "" { target c++23_down } }
+}
+
+static_assert (foo <42> () == 13);
@@ -21,7 +21,7 @@ void
foo (B a)
{
#pragma omp for collapse(2)
- for (auto [i, j, k] : a)
- for (int l = i; l < j; l += k) // { dg-error "initializer expression refers to iteration variable 'i'" }
- ; // { dg-error "condition expression refers to iteration variable 'j'" "" { target *-*-* } .-1 }
-} // { dg-error "increment expression refers to iteration variable 'k'" "" { target *-*-* } .-2 }
+ for (auto [i, j, k] : a) // { dg-error "initializer expression refers to iteration variable 'i'" }
+ for (int l = i; l < j; l += k) // { dg-error "condition expression refers to iteration variable 'j'" }
+ ; // { dg-error "increment expression refers to iteration variable 'k'" "" { target *-*-* } .-2 }
+}