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

Reply via email to