[PATCH v6 1/2] libstdc++: Add constructors and assignments for tuple<> with tuple-like types [PR119721]
Tomasz Kaminski
tkaminsk@redhat.com
Wed Oct 29 16:07:13 GMT 2025
On Wed, Oct 29, 2025 at 4:53 PM Jonathan Wakely <jwakely@redhat.com> wrote:
> On Mon, 27 Oct 2025 at 15:11 +0100, Tomasz Kamiński wrote:
> >From: Osama Abdelkader <osama.abdelkader@gmail.com>
> >
> >This patch adds support for constructing and assigning tuple<> from
> >other empty tuple-like types (e.g., array<T, 0>), completing the C++23
> >tuple-like interface for the zero-element tuple specialization.
> >
> >The implementation includes:
> >- Constructor from forwarding reference to tuple-like types
> >- Allocator-aware constructor from tuple-like types
> >- Assignment operator from tuple-like types
> >- Const assignment operator from tuple-like types
> >
> > PR libstdc++/119721
> >
> >libstdc++-v3/ChangeLog:
> >
> > * include/std/tuple (tuple<>::tuple(const tuple&))
> > (tuple<>::operator=(const tuple&)): Define as defaulted.
> > (tuple<>::swap): Moved the defintion after assignments.
> > (tuple<>::tuple(_UTuple&&))
> > (tuple<>::tuple(allocator_arg_t, const _Alloc&, _UTuple&&))
> > (tuple<>::operator=(_UTuple&&)) [__cpp_lib_tuple_like]: Define.
> > * testsuite/23_containers/tuple/cons/119721.cc: New test for
> > constructors and assignments with empty tuple-like types.
> > * testsuite/20_util/tuple/requirements/empty_trivial.cc:
> > New test verifying tuple<> remains trivially copyable.
> >
> >Co-authored-by: Tomasz Kamiński <tkaminsk@redhat.com>
> >Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com>
> >Signed-off-by: Tomasz Kamiński <tkaminsk@redhat.com>
> >---
> >v6:
> > - uses dg-run instead of dg-compile
> > - remove dg-option { "-std=c++23" } so the test run currently
> > in other modes
> > - remove test testing from const-assigment from tuple<>
> >
> >
> > libstdc++-v3/include/std/tuple | 44 ++++++-
> > .../tuple/requirements/empty_trivial.cc | 17 +++
> > .../23_containers/tuple/cons/119721.cc | 111 ++++++++++++++++++
> > 3 files changed, 167 insertions(+), 5 deletions(-)
> > create mode 100644
> libstdc++-v3/testsuite/20_util/tuple/requirements/empty_trivial.cc
> > create mode 100644
> libstdc++-v3/testsuite/23_containers/tuple/cons/119721.cc
> >
> >diff --git a/libstdc++-v3/include/std/tuple
> b/libstdc++-v3/include/std/tuple
> >index c064a92df4c..28a99ead499 100644
> >--- a/libstdc++-v3/include/std/tuple
> >+++ b/libstdc++-v3/include/std/tuple
> >@@ -1984,14 +1984,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > class tuple<>
> > {
> > public:
> >+ // We need the default since we're going to define no-op
> >+ // allocator constructors.
> >+ tuple() = default;
> >+ // Defaulted copy operations to maintain trivial copyability.
> >+ // and support non-const assignment expressions.
> >+ tuple(const tuple&) = default;
> >+ tuple& operator=(const tuple&) = default;
> >+
> > _GLIBCXX20_CONSTEXPR
> > void swap(tuple&) noexcept { /* no-op */ }
> >+
> > #if __cpp_lib_ranges_zip // >= C++23
> >- constexpr void swap(const tuple&) const noexcept { /* no-op */ }
> >+ constexpr void swap(const tuple&) const noexcept
> >+ { /* no-op */ }
> > #endif
> >- // We need the default since we're going to define no-op
> >- // allocator constructors.
> >- tuple() = default;
> >+
> > // No-op allocator constructors.
> > template<typename _Alloc>
> > _GLIBCXX20_CONSTEXPR
> >@@ -2001,7 +2009,33 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> > tuple(allocator_arg_t, const _Alloc&, const tuple&) noexcept { }
> >
> > #if __cpp_lib_tuple_like // >= C++23
> >- // Comparison operators for tuple<> with other empty tuple-like
> types
> >+ template<__tuple_like _UTuple>
> >+ requires (!is_same_v<remove_cvref_t<_UTuple>, tuple>
> >+ && !is_same_v<remove_cvref_t<_UTuple>, allocator_arg_t>
> >+ && tuple_size_v<remove_cvref_t<_UTuple>> == 0)
>
> I think this constraint will always instantiate all three variable
> templates, but if we wrote requires (!A) && (!B) && (C == 0) it would
> short circuit ... I think?
>
Was thinking about the same, but rewrite to atomic constrained by
normalization
https://eel.is/c++draft/temp.constr.normal, seem to not care about it. But
as we both
wondered about it, I will change that.
>
> It won't make a huge difference though, so not a problem.
>
> OK for trunk.
>
> >+ constexpr
> >+ tuple(_UTuple&&) noexcept { }
> >+
> >+ template<typename _Alloc, __tuple_like _UTuple>
> >+ requires (!is_same_v<remove_cvref_t<_UTuple>, tuple>
> >+ && tuple_size_v<remove_cvref_t<_UTuple>> == 0)
> >+ constexpr
> >+ tuple(allocator_arg_t, const _Alloc&, _UTuple&&) noexcept { }
> >+
> >+ template<__tuple_like _UTuple>
> >+ requires (!is_same_v<remove_cvref_t<_UTuple>, tuple>
> >+ && tuple_size_v<remove_cvref_t<_UTuple>> == 0)
> >+ constexpr tuple&
> >+ operator=(_UTuple&&) noexcept
> >+ { return *this; }
> >+
> >+ template<__tuple_like _UTuple>
> >+ requires (!is_same_v<remove_cvref_t<_UTuple>, tuple>
> >+ && tuple_size_v<remove_cvref_t<_UTuple>> == 0)
> >+ constexpr const tuple&
> >+ operator=(_UTuple&&) const noexcept
> >+ { return *this; }
> >+
> > template<__tuple_like _UTuple>
> > requires (!__is_tuple_v<_UTuple> && tuple_size_v<_UTuple> == 0)
> > [[nodiscard]]
> >diff --git
> a/libstdc++-v3/testsuite/20_util/tuple/requirements/empty_trivial.cc
> b/libstdc++-v3/testsuite/20_util/tuple/requirements/empty_trivial.cc
> >new file mode 100644
> >index 00000000000..ee18bb3145e
> >--- /dev/null
> >+++ b/libstdc++-v3/testsuite/20_util/tuple/requirements/empty_trivial.cc
> >@@ -0,0 +1,17 @@
> >+// { dg-do compile { target c++11 } }
> >+
> >+#include <tuple>
> >+#include <type_traits>
> >+
> >+// Check that tuple<> has the expected trivial properties
> >+static_assert(std::is_trivially_copyable<std::tuple<>>::value,
> >+ "tuple<> should be trivially copyable");
> >+static_assert(std::is_trivially_copy_constructible<std::tuple<>>::value,
> >+ "tuple<> should be trivially copy constructible");
> >+static_assert(std::is_trivially_move_constructible<std::tuple<>>::value,
> >+ "tuple<> should be trivially move constructible");
> >+static_assert(std::is_trivially_copy_assignable<std::tuple<>>::value,
> >+ "tuple<> should be trivially copy assignable");
> >+static_assert(std::is_trivially_move_assignable<std::tuple<>>::value,
> >+ "tuple<> should be trivially move assignable");
> >+
> >diff --git a/libstdc++-v3/testsuite/23_containers/tuple/cons/119721.cc
> b/libstdc++-v3/testsuite/23_containers/tuple/cons/119721.cc
> >new file mode 100644
> >index 00000000000..240f1a5e8d7
> >--- /dev/null
> >+++ b/libstdc++-v3/testsuite/23_containers/tuple/cons/119721.cc
> >@@ -0,0 +1,111 @@
> >+// { dg-do run { target c++23 } }
> >+
> >+// Test for PR libstdc++/119721: tuple<> construction/assignment with
> array<T, 0>
> >+
> >+#include <tuple>
> >+#include <array>
> >+#include <memory>
> >+#include <testsuite_hooks.h>
> >+
> >+constexpr void
> >+test01()
> >+{
> >+ std::array<int, 0> a{};
> >+
> >+ // Constructor from array<int, 0>
> >+ std::tuple<> t1(a);
> >+ std::tuple<> t2(std::move(a));
> >+
> >+ // Assignment from array<int, 0>
> >+ std::tuple<> t3;
> >+ t3 = a;
> >+ t3 = std::move(a);
> >+
> >+ VERIFY( t1 == a );
> >+ VERIFY( t2 == a );
> >+ VERIFY( t3 == a );
> >+}
> >+
> >+constexpr void
> >+test02()
> >+{
> >+ // Test with non-comparable element type
> >+ struct NonComparable
> >+ {
> >+ void operator==(const NonComparable&) const = delete;
> >+ void operator<=>(const NonComparable&) const = delete;
> >+ };
> >+
> >+ std::array<NonComparable, 0> a{};
> >+
> >+ std::tuple<> t1(a);
> >+ std::tuple<> t2(std::move(a));
> >+
> >+ std::tuple<> t3;
> >+ t3 = a;
> >+ t3 = std::move(a);
> >+
> >+ VERIFY( t1 == a );
> >+}
> >+
> >+constexpr void
> >+test03()
> >+{
> >+ // Test assignment return type (non-const assignment)
> >+ std::tuple<> t, u;
> >+ std::tuple<>& r1 = (t = u);
> >+ VERIFY( &r1 == &t );
> >+
> >+ std::tuple<>& r2 = (t = {});
> >+ VERIFY( &r2 == &t );
> >+
> >+ std::array<int, 0> a{};
> >+ std::tuple<>& r3 = (t = a);
> >+ VERIFY( &r3 == &t );
> >+}
> >+
> >+constexpr void
> >+test04()
> >+{
> >+ std::array<int, 0> a{};
> >+ const std::tuple<> t1;
> >+
> >+ // Const assignment from array
> >+ t1 = a;
> >+ t1 = std::move(a);
> >+
> >+ VERIFY( t1 == a );
> >+}
> >+
> >+void
> >+test05()
> >+{
> >+ std::array<int, 0> a{};
> >+ std::allocator<int> alloc;
> >+
> >+ // Allocator constructor from array
> >+ std::tuple<> t1(std::allocator_arg, alloc, a);
> >+ std::tuple<> t2(std::allocator_arg, alloc, std::move(a));
> >+
> >+ VERIFY( t1 == a );
> >+ VERIFY( t2 == a );
> >+}
> >+
> >+int main()
> >+{
> >+ auto test_all = [] {
> >+ test01();
> >+ test02();
> >+ test03();
> >+ test04();
> >+ return true;
> >+ };
> >+
> >+ test_all();
> >+ static_assert( test_all() );
> >+
> >+ // allocator test is not constexpr
> >+ test05();
> >+ return 0;
> >+}
> >+
> >--
> >2.51.0
> >
> >
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20251029/75952445/attachment.htm>
More information about the Libstdc++
mailing list