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
>
>

Reply via email to