https://gcc.gnu.org/g:52e300124d2eaad0f19ccc0a49f95fd605a1de69
commit r16-4710-g52e300124d2eaad0f19ccc0a49f95fd605a1de69 Author: Osama Abdelkader <[email protected]> Date: Sat Oct 25 20:25:42 2025 +0300 libstdc++: Add constructors and assignments for tuple<> with tuple-like types [PR119721] 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 <[email protected]> Co-authored-by: Tomasz Kamiński <[email protected]> Signed-off-by: Osama Abdelkader <[email protected]> Signed-off-by: Tomasz Kamiński <[email protected]> Diff: --- libstdc++-v3/include/std/tuple | 48 +++++++-- .../20_util/tuple/requirements/empty_trivial.cc | 17 ++++ .../testsuite/23_containers/tuple/cons/119721.cc | 111 +++++++++++++++++++++ 3 files changed, 169 insertions(+), 7 deletions(-) diff --git a/libstdc++-v3/include/std/tuple b/libstdc++-v3/include/std/tuple index c064a92df4c1..6edcf5e55536 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,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 index 000000000000..ee18bb3145ea --- /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 000000000000..240f1a5e8d78 --- /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; +} +
