On Wed, Oct 29, 2025 at 4:53 PM Jonathan Wakely <[email protected]> wrote:
> On Mon, 27 Oct 2025 at 15:11 +0100, Tomasz Kamiński wrote: > >From: Osama Abdelkader <[email protected]> > > > >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 <[email protected]> > >Signed-off-by: Osama Abdelkader <[email protected]> > >Signed-off-by: Tomasz Kamiński <[email protected]> > >--- > >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 > > > > > >
