Thanks for the second patch, we need to adjust the implementation a bit, so it does not break existing code.
On Fri, Oct 24, 2025 at 11:37 PM Osama Abdelkader < [email protected]> wrote: > This patch adds missing constructors and assignment operators for tuple<> > to support construction and assignment from other empty tuple-like types > such as array<T, 0>, completing the C++23 tuple-like support. > > The following members are added: > - tuple(_UTuple&&): Constructor from empty tuple-like types > - tuple(allocator_arg_t, const _Alloc&, _UTuple&&): Allocator constructor > - operator=(_UTuple&&): Assignment from empty tuple-like types > - operator=(const tuple&) const: Const copy assignment (matching swap) > - operator=(_UTuple&&) const: Const assignment from tuple-like types > > All operators are constexpr and noexcept, with appropriate constraints to > avoid ambiguity with existing constructors/assignments. > > Note: The addition of const assignment operators means tuple<> is no longer > trivially copyable in C++23, which is consistent with the general tuple > template that also has const assignment operators for ranges/zip support. This is not only ABI, but also API break as following no longer compilers tuple<> t, u; std::tuple<>& r = (t = u); Please add this test, preferably to some preexisting test. So this is not something we can really merge, but both be easily addressed by non-const defaulted assignment following: tuple& operator=(const tuple&) = default; Could you please do so, and add test checking if tuple<> for is_trivially_copyable, is_trivially_copy_constructible, is_trivially_move_constructible, is_trivially_copy_assignalbe, is_trivially_move_assignable, Prefferably to some existing test that run in C++11 mode. > > PR libstdc++/119721 > > libstdc++-v3/ChangeLog: > > * include/std/tuple (tuple<>::tuple(_UTuple&&)): Define. > (tuple<>::tuple(allocator_arg_t, const _Alloc&, _UTuple&&)): > Define. > (tuple<>::operator=(_UTuple&&)): Define. > (tuple<>::operator=(const tuple&) const): Define. > (tuple<>::operator=(_UTuple&&) const): Define. > * testsuite/23_containers/tuple/cons/119721.cc: New test. > > Suggested-by: Tomasz Kamiński <[email protected]> > Signed-off-by: Osama Abdelkader <[email protected]> > --- > libstdc++-v3/include/std/tuple | 32 +++++- > .../23_containers/tuple/cons/119721.cc | 102 ++++++++++++++++++ > 2 files changed, 133 insertions(+), 1 deletion(-) > 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..a78455d62 100644 > --- a/libstdc++-v3/include/std/tuple > +++ b/libstdc++-v3/include/std/tuple > @@ -2001,7 +2001,37 @@ _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; } > + > + constexpr const tuple& > + operator=(const tuple&) const noexcept > + { return *this; } > There is another block of __cpp_lib_tuple_like for swap at the beginning of the class, could you move this change above, as this is more related to being proxy, than construction from tuple-like. Also add already mentioned defaulted operator& > + > + 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/23_containers/tuple/cons/119721.cc > b/libstdc++-v3/testsuite/23_containers/tuple/cons/119721.cc > new file mode 100644 > index 000000000..69f6d4d43 > --- /dev/null > +++ b/libstdc++-v3/testsuite/23_containers/tuple/cons/119721.cc > @@ -0,0 +1,102 @@ > +// { 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() > +{ > + 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 > +test04() > +{ > + 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 ); > +} > + > +// Note: In C++23, tuple<> is no longer trivially copyable due to > +// the const copy assignment operator added to match swap(). > + > +int main() > +{ > + auto test_all = [] { > + test01(); > + test02(); > + test03(); > + }; > + > + test_all(); > + static_VERIFY( test_all() ); > + > + // allocator test is not constexpr > + test04(); > + return 0; > +} > + > -- > 2.43.0 > >
