libstdc++: Constrain tuple(tuple&&) [PR78302]

Message ID 20260506141443.399248-1-jwakely@redhat.com
State New
Headers
Series libstdc++: Constrain tuple(tuple&&) [PR78302] |

Commit Message

Jonathan Wakely May 6, 2026, 2:14 p.m. UTC
  Since C++20 the std::tuple move constructor should be constrained (as
modified by LWG 2899).

We already define the move constructor as defaulted, but it's not
implicitly defined as deleted for non-move-constructible element types
because the _Tuple_impl(_Tuple_impl&&) constructor is user-provided and
unconstrained. For C++20 and later we use a requires-clause to constrain
the defaulted tuple(tuple&&) constructor.

Ideally we'd make this change pre-C++20 as well, but that's harder to do
without using a requires-clause, so this change is only for C++20 and
later. I think that's OK, but if we need to change it for pre-C++20
later we can consider inheriting from _Enable_copy_move<..., tuple> to
make the defaulted move constructor defined as deleted.

libstdc++-v3/ChangeLog:

	PR libstdc++/78302
	PR libstdc++/71301
	* include/std/tuple [C++20] (tuple(tuple&&)): Add
	requires-clause.
	* testsuite/20_util/tuple/cons/78302.cc: New test.
---

Tested x86_64-linux.

 libstdc++-v3/include/std/tuple                     |  4 +++-
 libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc | 11 +++++++++++
 2 files changed, 14 insertions(+), 1 deletion(-)
 create mode 100644 libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
  

Comments

Tomasz Kaminski May 6, 2026, 2:48 p.m. UTC | #1
On Wed, May 6, 2026 at 4:16 PM Jonathan Wakely <jwakely@redhat.com> wrote:

> Since C++20 the std::tuple move constructor should be constrained (as
> modified by LWG 2899).
>
> We already define the move constructor as defaulted, but it's not
> implicitly defined as deleted for non-move-constructible element types
> because the _Tuple_impl(_Tuple_impl&&) constructor is user-provided and
> unconstrained. For C++20 and later we use a requires-clause to constrain
> the defaulted tuple(tuple&&) constructor.
>
> Ideally we'd make this change pre-C++20 as well, but that's harder to do
> without using a requires-clause, so this change is only for C++20 and
> later. I think that's OK, but if we need to change it for pre-C++20
> later we can consider inheriting from _Enable_copy_move<..., tuple> to
> make the defaulted move constructor defined as deleted.
>
> libstdc++-v3/ChangeLog:
>
>         PR libstdc++/78302
>         PR libstdc++/71301
>         * include/std/tuple [C++20] (tuple(tuple&&)): Add
>         requires-clause.
>         * testsuite/20_util/tuple/cons/78302.cc: New test.
> ---
>
> Tested x86_64-linux.
>
>  libstdc++-v3/include/std/tuple                     |  4 +++-
>  libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc | 11 +++++++++++
>  2 files changed, 14 insertions(+), 1 deletion(-)
>  create mode 100644 libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
>
> diff --git a/libstdc++-v3/include/std/tuple
> b/libstdc++-v3/include/std/tuple
> index cbacd5a3c977..64b96fe4f599 100644
> --- a/libstdc++-v3/include/std/tuple
> +++ b/libstdc++-v3/include/std/tuple
> @@ -954,7 +954,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>
>        constexpr tuple(const tuple&) = default;
>
> -      constexpr tuple(tuple&&) = default;
> +      constexpr
> +      tuple(tuple&&) requires (is_move_constructible_v<_Elements> && ...)
>
Could you add test for tuple of references (lvalue and rvalue)? I think the
traits
gives correct result,  but I am not sure.

> +       = default;
>
>        template<typename... _UTypes>
>         requires (__constructible<const _UTypes&...>())
> diff --git a/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
> b/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
> new file mode 100644
> index 000000000000..b3c6bd67fd27
> --- /dev/null
> +++ b/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
> @@ -0,0 +1,11 @@
> +// { dg-do compile { target c++20 } }
> +
> +// Bug 78302 is_move_constructible_v<tuple<nonmovable>> should be false
> +// LWG 2899. is_(nothrow_)move_constructible and tuple, optional and
> unique_ptr
> +
> +#include <tuple>
> +#include <type_traits>
> +
> +struct NotMovable { NotMovable(NotMovable&&) = delete; };
> +static_assert(!std::is_move_constructible_v<std::tuple<NotMovable>>);
> +static_assert(std::is_nothrow_move_constructible_v<std::tuple<int>>);
> --
> 2.54.0
>
>
  
Jonathan Wakely May 6, 2026, 8:10 p.m. UTC | #2
On Wed, 6 May 2026 at 15:49, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> On Wed, May 6, 2026 at 4:16 PM Jonathan Wakely <jwakely@redhat.com> wrote:
>>
>> Since C++20 the std::tuple move constructor should be constrained (as
>> modified by LWG 2899).
>>
>> We already define the move constructor as defaulted, but it's not
>> implicitly defined as deleted for non-move-constructible element types
>> because the _Tuple_impl(_Tuple_impl&&) constructor is user-provided and
>> unconstrained. For C++20 and later we use a requires-clause to constrain
>> the defaulted tuple(tuple&&) constructor.
>>
>> Ideally we'd make this change pre-C++20 as well, but that's harder to do
>> without using a requires-clause, so this change is only for C++20 and
>> later. I think that's OK, but if we need to change it for pre-C++20
>> later we can consider inheriting from _Enable_copy_move<..., tuple> to
>> make the defaulted move constructor defined as deleted.
>>
>> libstdc++-v3/ChangeLog:
>>
>>         PR libstdc++/78302
>>         PR libstdc++/71301
>>         * include/std/tuple [C++20] (tuple(tuple&&)): Add
>>         requires-clause.
>>         * testsuite/20_util/tuple/cons/78302.cc: New test.
>> ---
>>
>> Tested x86_64-linux.
>>
>>  libstdc++-v3/include/std/tuple                     |  4 +++-
>>  libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc | 11 +++++++++++
>>  2 files changed, 14 insertions(+), 1 deletion(-)
>>  create mode 100644 libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
>>
>> diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
>> index cbacd5a3c977..64b96fe4f599 100644
>> --- a/libstdc++-v3/include/std/tuple
>> +++ b/libstdc++-v3/include/std/tuple
>> @@ -954,7 +954,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>
>>        constexpr tuple(const tuple&) = default;
>>
>> -      constexpr tuple(tuple&&) = default;
>> +      constexpr
>> +      tuple(tuple&&) requires (is_move_constructible_v<_Elements> && ...)
>
> Could you add test for tuple of references (lvalue and rvalue)? I think the traits
> gives correct result,  but I am not sure.
>>
>> +       = default;
>>
>>        template<typename... _UTypes>
>>         requires (__constructible<const _UTypes&...>())
>> diff --git a/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc b/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
>> new file mode 100644
>> index 000000000000..b3c6bd67fd27
>> --- /dev/null
>> +++ b/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
>> @@ -0,0 +1,11 @@
>> +// { dg-do compile { target c++20 } }
>> +
>> +// Bug 78302 is_move_constructible_v<tuple<nonmovable>> should be false
>> +// LWG 2899. is_(nothrow_)move_constructible and tuple, optional and unique_ptr
>> +
>> +#include <tuple>
>> +#include <type_traits>
>> +
>> +struct NotMovable { NotMovable(NotMovable&&) = delete; };
>> +static_assert(!std::is_move_constructible_v<std::tuple<NotMovable>>);
>> +static_assert(std::is_nothrow_move_constructible_v<std::tuple<int>>);

Is this good enough?


#include <tuple>
#include <type_traits>

struct NotMovable { NotMovable(NotMovable&&) = delete; };
static_assert(!std::is_move_constructible_v<std::tuple<NotMovable>>);
static_assert(!std::is_move_constructible_v<std::tuple<int, NotMovable>>);
static_assert(!std::is_move_constructible_v<std::tuple<int&, NotMovable>>);
static_assert(!std::is_move_constructible_v<std::tuple<int&&, NotMovable>>);
static_assert(std::is_nothrow_move_constructible_v<std::tuple<int>>);
static_assert(std::is_nothrow_move_constructible_v<std::tuple<int&>>);
static_assert(std::is_nothrow_move_constructible_v<std::tuple<int&&>>);
static_assert(std::is_nothrow_move_constructible_v<std::tuple<int&&, int&>>);

(It passes)
  
Tomasz Kaminski May 7, 2026, 5:38 a.m. UTC | #3
On Wed, May 6, 2026 at 10:10 PM Jonathan Wakely <jwakely@redhat.com> wrote:

> On Wed, 6 May 2026 at 15:49, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
> >
> >
> >
> > On Wed, May 6, 2026 at 4:16 PM Jonathan Wakely <jwakely@redhat.com>
> wrote:
> >>
> >> Since C++20 the std::tuple move constructor should be constrained (as
> >> modified by LWG 2899).
> >>
> >> We already define the move constructor as defaulted, but it's not
> >> implicitly defined as deleted for non-move-constructible element types
> >> because the _Tuple_impl(_Tuple_impl&&) constructor is user-provided and
> >> unconstrained. For C++20 and later we use a requires-clause to constrain
> >> the defaulted tuple(tuple&&) constructor.
> >>
> >> Ideally we'd make this change pre-C++20 as well, but that's harder to do
> >> without using a requires-clause, so this change is only for C++20 and
> >> later. I think that's OK, but if we need to change it for pre-C++20
> >> later we can consider inheriting from _Enable_copy_move<..., tuple> to
> >> make the defaulted move constructor defined as deleted.
> >>
> >> libstdc++-v3/ChangeLog:
> >>
> >>         PR libstdc++/78302
> >>         PR libstdc++/71301
> >>         * include/std/tuple [C++20] (tuple(tuple&&)): Add
> >>         requires-clause.
> >>         * testsuite/20_util/tuple/cons/78302.cc: New test.
> >> ---
> >>
> >> Tested x86_64-linux.
> >>
> >>  libstdc++-v3/include/std/tuple                     |  4 +++-
> >>  libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc | 11 +++++++++++
> >>  2 files changed, 14 insertions(+), 1 deletion(-)
> >>  create mode 100644 libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
> >>
> >> diff --git a/libstdc++-v3/include/std/tuple
> b/libstdc++-v3/include/std/tuple
> >> index cbacd5a3c977..64b96fe4f599 100644
> >> --- a/libstdc++-v3/include/std/tuple
> >> +++ b/libstdc++-v3/include/std/tuple
> >> @@ -954,7 +954,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> >>
> >>        constexpr tuple(const tuple&) = default;
> >>
> >> -      constexpr tuple(tuple&&) = default;
> >> +      constexpr
> >> +      tuple(tuple&&) requires (is_move_constructible_v<_Elements> &&
> ...)
> >
> > Could you add test for tuple of references (lvalue and rvalue)? I think
> the traits
> > gives correct result,  but I am not sure.
> >>
> >> +       = default;
> >>
> >>        template<typename... _UTypes>
> >>         requires (__constructible<const _UTypes&...>())
> >> diff --git a/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
> b/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
> >> new file mode 100644
> >> index 000000000000..b3c6bd67fd27
> >> --- /dev/null
> >> +++ b/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
> >> @@ -0,0 +1,11 @@
> >> +// { dg-do compile { target c++20 } }
> >> +
> >> +// Bug 78302 is_move_constructible_v<tuple<nonmovable>> should be false
> >> +// LWG 2899. is_(nothrow_)move_constructible and tuple, optional and
> unique_ptr
> >> +
> >> +#include <tuple>
> >> +#include <type_traits>
> >> +
> >> +struct NotMovable { NotMovable(NotMovable&&) = delete; };
> >> +static_assert(!std::is_move_constructible_v<std::tuple<NotMovable>>);
> >> +static_assert(std::is_nothrow_move_constructible_v<std::tuple<int>>);
>
> Is this good enough?
>
>
> #include <tuple>
> #include <type_traits>
>
> struct NotMovable { NotMovable(NotMovable&&) = delete; };
> static_assert(!std::is_move_constructible_v<std::tuple<NotMovable>>);
> static_assert(!std::is_move_constructible_v<std::tuple<int, NotMovable>>);
> static_assert(!std::is_move_constructible_v<std::tuple<int&, NotMovable>>);
> static_assert(!std::is_move_constructible_v<std::tuple<int&&,
> NotMovable>>);
> static_assert(std::is_nothrow_move_constructible_v<std::tuple<int>>);
> static_assert(std::is_nothrow_move_constructible_v<std::tuple<int&>>);
> static_assert(std::is_nothrow_move_constructible_v<std::tuple<int&&>>);
> static_assert(std::is_nothrow_move_constructible_v<std::tuple<int&&,
> int&>>);
>
Add them to test file, so I do not need to check is_move_constructible_v on
reference,
when looking at it. LGTM with that.

>
> (It passes)
>
>
  
Jonathan Wakely May 7, 2026, 8:34 a.m. UTC | #4
On Thu, 7 May 2026 at 06:39, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>
>
>
> On Wed, May 6, 2026 at 10:10 PM Jonathan Wakely <jwakely@redhat.com> wrote:
>>
>> On Wed, 6 May 2026 at 15:49, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>> >
>> >
>> >
>> > On Wed, May 6, 2026 at 4:16 PM Jonathan Wakely <jwakely@redhat.com> wrote:
>> >>
>> >> Since C++20 the std::tuple move constructor should be constrained (as
>> >> modified by LWG 2899).
>> >>
>> >> We already define the move constructor as defaulted, but it's not
>> >> implicitly defined as deleted for non-move-constructible element types
>> >> because the _Tuple_impl(_Tuple_impl&&) constructor is user-provided and
>> >> unconstrained. For C++20 and later we use a requires-clause to constrain
>> >> the defaulted tuple(tuple&&) constructor.
>> >>
>> >> Ideally we'd make this change pre-C++20 as well, but that's harder to do
>> >> without using a requires-clause, so this change is only for C++20 and
>> >> later. I think that's OK, but if we need to change it for pre-C++20
>> >> later we can consider inheriting from _Enable_copy_move<..., tuple> to
>> >> make the defaulted move constructor defined as deleted.
>> >>
>> >> libstdc++-v3/ChangeLog:
>> >>
>> >>         PR libstdc++/78302
>> >>         PR libstdc++/71301
>> >>         * include/std/tuple [C++20] (tuple(tuple&&)): Add
>> >>         requires-clause.
>> >>         * testsuite/20_util/tuple/cons/78302.cc: New test.
>> >> ---
>> >>
>> >> Tested x86_64-linux.
>> >>
>> >>  libstdc++-v3/include/std/tuple                     |  4 +++-
>> >>  libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc | 11 +++++++++++
>> >>  2 files changed, 14 insertions(+), 1 deletion(-)
>> >>  create mode 100644 libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
>> >>
>> >> diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
>> >> index cbacd5a3c977..64b96fe4f599 100644
>> >> --- a/libstdc++-v3/include/std/tuple
>> >> +++ b/libstdc++-v3/include/std/tuple
>> >> @@ -954,7 +954,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>> >>
>> >>        constexpr tuple(const tuple&) = default;
>> >>
>> >> -      constexpr tuple(tuple&&) = default;
>> >> +      constexpr
>> >> +      tuple(tuple&&) requires (is_move_constructible_v<_Elements> && ...)
>> >
>> > Could you add test for tuple of references (lvalue and rvalue)? I think the traits
>> > gives correct result,  but I am not sure.
>> >>
>> >> +       = default;
>> >>
>> >>        template<typename... _UTypes>
>> >>         requires (__constructible<const _UTypes&...>())
>> >> diff --git a/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc b/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
>> >> new file mode 100644
>> >> index 000000000000..b3c6bd67fd27
>> >> --- /dev/null
>> >> +++ b/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
>> >> @@ -0,0 +1,11 @@
>> >> +// { dg-do compile { target c++20 } }
>> >> +
>> >> +// Bug 78302 is_move_constructible_v<tuple<nonmovable>> should be false
>> >> +// LWG 2899. is_(nothrow_)move_constructible and tuple, optional and unique_ptr
>> >> +
>> >> +#include <tuple>
>> >> +#include <type_traits>
>> >> +
>> >> +struct NotMovable { NotMovable(NotMovable&&) = delete; };
>> >> +static_assert(!std::is_move_constructible_v<std::tuple<NotMovable>>);
>> >> +static_assert(std::is_nothrow_move_constructible_v<std::tuple<int>>);
>>
>> Is this good enough?
>>
>>
>> #include <tuple>
>> #include <type_traits>
>>
>> struct NotMovable { NotMovable(NotMovable&&) = delete; };
>> static_assert(!std::is_move_constructible_v<std::tuple<NotMovable>>);
>> static_assert(!std::is_move_constructible_v<std::tuple<int, NotMovable>>);
>> static_assert(!std::is_move_constructible_v<std::tuple<int&, NotMovable>>);
>> static_assert(!std::is_move_constructible_v<std::tuple<int&&, NotMovable>>);
>> static_assert(std::is_nothrow_move_constructible_v<std::tuple<int>>);
>> static_assert(std::is_nothrow_move_constructible_v<std::tuple<int&>>);
>> static_assert(std::is_nothrow_move_constructible_v<std::tuple<int&&>>);
>> static_assert(std::is_nothrow_move_constructible_v<std::tuple<int&&, int&>>);
>
> Add them to test file, so I do not need to check is_move_constructible_v on reference,

Yes, what I pasted above is what is in the test file in my local tree.
I've pushed it to trunk and will backport it after waiting a while.

> when looking at it. LGTM with that.
>>
>>
>> (It passes)
>>
  

Patch

diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple
index cbacd5a3c977..64b96fe4f599 100644
--- a/libstdc++-v3/include/std/tuple
+++ b/libstdc++-v3/include/std/tuple
@@ -954,7 +954,9 @@  _GLIBCXX_BEGIN_NAMESPACE_VERSION
 
       constexpr tuple(const tuple&) = default;
 
-      constexpr tuple(tuple&&) = default;
+      constexpr
+      tuple(tuple&&) requires (is_move_constructible_v<_Elements> && ...)
+	= default;
 
       template<typename... _UTypes>
 	requires (__constructible<const _UTypes&...>())
diff --git a/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc b/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
new file mode 100644
index 000000000000..b3c6bd67fd27
--- /dev/null
+++ b/libstdc++-v3/testsuite/20_util/tuple/cons/78302.cc
@@ -0,0 +1,11 @@ 
+// { dg-do compile { target c++20 } }
+
+// Bug 78302 is_move_constructible_v<tuple<nonmovable>> should be false
+// LWG 2899. is_(nothrow_)move_constructible and tuple, optional and unique_ptr
+
+#include <tuple>
+#include <type_traits>
+
+struct NotMovable { NotMovable(NotMovable&&) = delete; };
+static_assert(!std::is_move_constructible_v<std::tuple<NotMovable>>);
+static_assert(std::is_nothrow_move_constructible_v<std::tuple<int>>);