]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
libstdc++: Add constructors and assignments for tuple<> with tuple-like types [PR119721]
authorOsama Abdelkader <osama.abdelkader@gmail.com>
Sat, 25 Oct 2025 17:25:42 +0000 (20:25 +0300)
committerTomasz Kamiński <tkaminsk@redhat.com>
Wed, 29 Oct 2025 16:09:15 +0000 (17:09 +0100)
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.
(tuple<>::operator==, tuple<>::opeator<=>): Parenthesize
constrains individually.
* 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.

Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
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>
libstdc++-v3/include/std/tuple
libstdc++-v3/testsuite/20_util/tuple/requirements/empty_trivial.cc [new file with mode: 0644]
libstdc++-v3/testsuite/23_containers/tuple/cons/119721.cc [new file with mode: 0644]

index c064a92df4c1a08be27b8f454e0c31cfd2101733..6edcf5e55536b7ec8bac570ea899125f18058b09 100644 (file)
@@ -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,16 +2009,42 @@ _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_tuple_v<_UTuple> && tuple_size_v<_UTuple> == 0)
+       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)
+      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]]
       friend constexpr bool
       operator==(const tuple&, const _UTuple&) noexcept
       { return true; }
 
       template<__tuple_like _UTuple>
-       requires (!__is_tuple_v<_UTuple> && tuple_size_v<_UTuple> == 0)
+       requires (!__is_tuple_v<_UTuple>) && (tuple_size_v<_UTuple> == 0)
       friend constexpr strong_ordering
       operator<=>(const tuple&, const _UTuple&) noexcept
       { return strong_ordering::equal; }
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 (file)
index 0000000..ee18bb3
--- /dev/null
@@ -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 (file)
index 0000000..240f1a5
--- /dev/null
@@ -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;
+}
+