On Mon, Oct 27, 2025 at 1:11 PM Osama Abdelkader <[email protected]> wrote:
> 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. > * > libstdc++-v3/testsuite/20_util/tuple/requirements/empty_trivial.cc: > New test verifying tuple<> remains trivially copyable. > > Reviewed-by: Jonathan Wakely <[email protected]> > Co-authored-by: Tomasz Kamiński <[email protected]> > Signed-off-by: Tomasz Kamiński <[email protected]> > Signed-off-by: Osama Abdelkader <[email protected]> > --- > v4: > - Fixed a typo > - Relocated empty_trivial test to the correct location > The trival_empty.cc test seem to fail, and reading of https://eel.is/c++draft/class.copy.assign#1, seem to indicate that following is also a copy-assignment operator, however it cannot be defaulted. + constexpr const tuple& + operator=(const tuple&) const noexcept + { return *this; } I will post a next version of this patch with const-copy assignment removed, and investigate possible fixes. > --- > libstdc++-v3/include/std/tuple | 49 +++++++- > .../tuple/requirements/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/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 c064a92df..30db3b785 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 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 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/requirements/empty_trivial.cc > b/libstdc++-v3/testsuite/20_util/tuple/requirements/empty_trivial.cc > new file mode 100644 > index 000000000..ee18bb314 > --- /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 000000000..e87845c6b > --- /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.43.0 > >
