[PATCH v2] libstdc++: Add constructors and assignments for tuple<> with tuple-like types [PR119721]
Tomasz Kaminski
tkaminsk@redhat.com
Mon Oct 27 08:24:37 GMT 2025
On Sat, Oct 25, 2025 at 7:26 PM Osama Abdelkader <osama.abdelkader@gmail.com>
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
> - Const copy assignment operator for consistency with swap
> - Defaulted copy assignment to maintain trivial copyability
>
> 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
>
> The const copy assignment operator is placed near swap() as it serves
> a similar purpose for proxy reference semantics in C++23.
>
> PR libstdc++/119721
>
> libstdc++-v3/ChangeLog:
>
> * include/std/tuple (tuple<>): Add constructor and allocator-aware
> constructor from tuple-like types. Add assignment operators from
> tuple-like types. Add const copy assignment operator for proxy
> reference semantics. Add defaulted copy assignment to maintain
> trivial copyability.
> * testsuite/23_containers/tuple/cons/119721.cc: New test for
> constructors and assignments with empty tuple-like types.
> * testsuite/20_util/tuple/element_access/trivial_empty_tuple.cc:
> New test verifying tuple<> remains trivially copyable.
>
> Signed-off-by: Osama Abdelkader <osama.abdelkader@gmail.com>
>
The changes looks good to me. I have few minor suggestion, that I will apply
and post v3.
> ---
> v2:
> - Fix trivial copyability of tuple<>.
> - Add test for trivial copyability of tuple<>.
> ---
> libstdc++-v3/include/std/tuple | 40 ++++++-
> .../element_access/trivial_empty_tuple.cc | 19 +++
> .../23_containers/tuple/cons/119721.cc | 113 ++++++++++++++++++
> 3 files changed, 171 insertions(+), 1 deletion(-)
> create mode 100644
> libstdc++-v3/testsuite/20_util/tuple/element_access/trivial_empty_tuple.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..af7ffdf40 100644
> --- a/libstdc++-v3/include/std/tuple
> +++ b/libstdc++-v3/include/std/tuple
> @@ -1989,9 +1989,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> #if __cpp_lib_ranges_zip // >= C++23
> constexpr void swap(const tuple&) const noexcept { /* no-op */ }
> #endif
> +
> +#if __cpp_lib_tuple_like // >= C++23
This change was part of p2321r2.html with the swap, so we should guard
it with __cpp_lib_ranges_zip, i.e. merge with above block
>
+ // Const copy assignment operator for consistency with swap and other
> + // const member functions in C++23. This supports proxy reference
> semantics.
> + constexpr const tuple&
> + operator=(const tuple&) const noexcept
> + { return *this; }
> +#endif
> +
> // We need the default since we're going to define no-op
> // allocator constructors.
> tuple() = default;
> + // Defaulted copy assignment operator to maintain trivial
> copyability
> + // and support non-const assignment expressions.
> + tuple& operator=(const tuple&) = default;
>
I would also add default copy-constructor, it's generation is deprecated.
> // No-op allocator constructors.
> template<typename _Alloc>
> _GLIBCXX20_CONSTEXPR
> @@ -2001,7 +2013,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/element_access/trivial_empty_tuple.cc
> b/libstdc++-v3/testsuite/20_util/tuple/element_access/trivial_empty_tuple.cc
> new file mode 100644
> index 000000000..7ec74740b
> --- /dev/null
> +++
> b/libstdc++-v3/testsuite/20_util/tuple/element_access/trivial_empty_tuple.cc
>
I am gone move it to just tuple directory.
> @@ -0,0 +1,19 @@
> +// { dg-do compile { target c++11 } }
> +
> +// Test that tuple<> remains trivially copyable with defaulted assignment
> +
> +#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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://gcc.gnu.org/pipermail/libstdc++/attachments/20251027/e1d16c68/attachment.htm>
More information about the Libstdc++
mailing list