On Mon, Oct 27, 2025 at 11:11:30AM +0000, Jonathan Wakely wrote:
> On Mon, 27 Oct 2025 at 09:54 +0100, Tomasz Kamiński wrote:
> > From: Osama Abdelkader <[email protected]>
> > 
> > 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.
> >     * testsuite/20_util/tuple/empty_trivial.cc:
> >     New test verifying tuple<> remains trivially copyable.
> > 
> > Co-authored-by: Tomasz Kamiński <[email protected]>
> > Signed-off-by: Osama Abdelkader <[email protected]>
> > Signed-off-by: Tomasz Kamiński <[email protected]>
> > ---
> > v3:
> > - defines const assigment under __cpp_lib_ranges_zip
> > - updates commit message and description
> > - add default copy coonstructor
> > - move test for triviality to tuple directory
> > 
> > We define default copy constructo and assigment unconditionaly,
> > and in consequence the move operations are not longer generated.
> > As for empty tuple<>, copy operations are trivial, this does not change
> > the visible behavior. However, explicit tuple<> instantiations will
> > no longer define this operations. This will reduce number of symbols
> > to be linked. I believe we are generally fine with such change, but calling
> > this explicitly.
> > 
> > Testex on x86_64-linux. OK for trunk?
> 
> OK with one spelling fix and one file renamed, see below.
> 
> 
> > libstdc++-v3/include/std/tuple                |  49 +++++++-
> > .../testsuite/20_util/tuple/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/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 c064a92df4c..db2ca29868a 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 operatoions to maintain trivial copyability.
> 
> "operations"
> 
> > +      // 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/empty_trivial.cc 
> > b/libstdc++-v3/testsuite/20_util/tuple/empty_trivial.cc
> > new file mode 100644
> > index 00000000000..ee18bb3145e
> > --- /dev/null
> > +++ b/libstdc++-v3/testsuite/20_util/tuple/empty_trivial.cc
> 
> I think this new file makes sense under the
> testsuite/20_util/tuple/requirements/ directory.
> 
> Don't forget to update the ChangeLog for the new name.
> 

Thanks Tomasz for the updated patch, and thanks Jonathan for the review.
I just sent v4 with these changes.

Thank you,
Osama

> 
> > @@ -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 00000000000..e87845c6bda
> > --- /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.51.0
> > 
> > 
> 

Reply via email to