[PATCH v3] libstdc++: Add constructors and assignments for tuple<> with tuple-like types [PR119721]

Jonathan Wakely jwakely@redhat.com
Mon Oct 27 11:11:30 GMT 2025


On Mon, 27 Oct 2025 at 09:54 +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
>
>Furthermore it also includes:
>- Const copy assignment operator
>- Defaulted copy constructor and assignment
>This is remainder of P2321R2, that clarified for proxy reference
>semantics in C++23, and thus is placed together with swap.
>
>The defaulted copy assignment operator is crucial to:
>1. Maintain trivial copyability of tuple<> (all 5 trivial traits)
>2. Support non-const assignment expressions: tuple<>& r = (t = u)
>3. Avoid API/ABI breaks with existing code
>
>	PR libstdc++/119721
>
>libstdc++-v3/ChangeLog:
>
>	* include/std/tuple (tuple<>::tuple(const tuple&))
>	(tuple<>::operator=(const tuple&)): Define as defaulted.
>	(tuple<>::operator=(const tuple&) const) [__cpp_lib_ranges_zip]:
>	Define
>	(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/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>
>---
>v3:
>- defines const assigment under __cpp_lib_ranges_zip
>- updates commit message and description
>- add default copy coonstructor
>- move test for triviality to tuple directory
>
>We define default copy constructo and assigment unconditionaly,
>and in consequence the move operations are not longer generated.
>As for empty tuple<>, copy operations are trivial, this does not change
>the visible behavior. However, explicit tuple<> instantiations will
>no longer define this operations. This will reduce number of symbols
>to be linked. I believe we are generally fine with such change, but calling
>this explicitly.
>
>Testex on x86_64-linux. OK for trunk?

OK with one spelling fix and one file renamed, see below.


> libstdc++-v3/include/std/tuple                |  49 +++++++-
> .../testsuite/20_util/tuple/empty_trivial.cc  |  17 +++
> .../23_containers/tuple/cons/119721.cc        | 113 ++++++++++++++++++
> 3 files changed, 174 insertions(+), 5 deletions(-)
> create mode 100644 libstdc++-v3/testsuite/20_util/tuple/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..db2ca29868a 100644
>--- a/libstdc++-v3/include/std/tuple
>+++ b/libstdc++-v3/include/std/tuple
>@@ -1984,14 +1984,27 @@ _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 operatoions to maintain trivial copyability.

"operations"

>+      // 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 const tuple&
>+      operator=(const tuple&) const noexcept
>+      { return *this; }
>+
>+      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 +2014,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)
>+      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/empty_trivial.cc b/libstdc++-v3/testsuite/20_util/tuple/empty_trivial.cc
>new file mode 100644
>index 00000000000..ee18bb3145e
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/20_util/tuple/empty_trivial.cc

I think this new file makes sense under the
testsuite/20_util/tuple/requirements/ directory.

Don't forget to update the ChangeLog for the new name.


>@@ -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..e87845c6bda
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/23_containers/tuple/cons/119721.cc
>@@ -0,0 +1,113 @@
>+// { dg-do compile { target c++23 } }
>+// { dg-options "-std=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<>& r = (t = u);
>+  VERIFY( &r == &t );
>+
>+  std::array<int, 0> a{};
>+  std::tuple<>& r2 = (t = a);
>+  VERIFY( &r2 == &t );
>+}
>+
>+constexpr void
>+test04()
>+{
>+  std::array<int, 0> a{};
>+  const std::tuple<> t1;
>+
>+  // Const copy assignment from tuple
>+  std::tuple<> t2;
>+  t1 = t2;
>+
>+  // Const assignment from array
>+  t1 = a;
>+  t1 = std::move(a);
>+
>+  VERIFY( t1 == t2 );
>+  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();
>+  };
>+
>+  test_all();
>+  static_VERIFY( test_all() );
>+
>+  // allocator test is not constexpr
>+  test05();
>+  return 0;
>+}
>+
>-- 
>2.51.0
>
>



More information about the Libstdc++ mailing list