On Sat, Oct 18, 2025 at 9:51 PM Osama Abdelkader <[email protected]> wrote:
> This fixes the C++23 compliance issue where std::tuple<> cannot be compared > with other empty tuple-like types such as std::array<T, 0>. > > The operators correctly allow comparison with array<T, 0> even when T is > not > comparable, because empty tuple-like types don't compare element values. > > libstdc++-v3/ChangeLog: > > PR libstdc++/119721 > * include/std/tuple: Add tuple<> comparison operators for > empty tuple-like types. > * testsuite/23_containers/tuple/comparison_operators/119721.cc: > New test. > > Signed-off-by: Osama Abdelkader <[email protected]> > --- > v4: > - Added testsuite test > v3: > - Added noexcept specifiers to the operators > v2: > - Replaced explicit array<T, 0> operators with generic tuple-like operators > - Only operator== and operator<=> are provided > - Operators work with any empty tuple-like type > - No need for reversed argument order > --- > libstdc++-v3/include/std/tuple | 19 +++++ > .../tuple/comparison_operators/119721.cc | 71 +++++++++++++++++++ > 2 files changed, 90 insertions(+) > create mode 100644 > libstdc++-v3/testsuite/23_containers/tuple/comparison_operators/119721.cc > > diff --git a/libstdc++-v3/include/std/tuple > b/libstdc++-v3/include/std/tuple > index 0ca616f1b..0709cf7b3 100644 > --- a/libstdc++-v3/include/std/tuple > +++ b/libstdc++-v3/include/std/tuple > @@ -2001,6 +2001,25 @@ _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 > + // Note: These operators allow comparison with any empty tuple-like > type, > + // including array<T, 0> and span<T, 0>, where T may not be comparable. > + // This is correct because empty tuple-like types don't compare > elements. > + template<__tuple_like _UTuple> > + requires (!__is_tuple_v<_UTuple> && tuple_size_v<_UTuple> == 0) > + [[nodiscard]] > + constexpr bool > + operator==(const tuple<>&, const _UTuple&) noexcept > + { return true; } > + > + template<__tuple_like _UTuple> > + requires (!__is_tuple_v<_UTuple> && tuple_size_v<_UTuple> == 0) > + constexpr strong_ordering > + operator<=>(const tuple<>&, const _UTuple&) noexcept > + { return strong_ordering::equal; } > +#endif // C++23 Thanks for the update, this looks good to me, only one minor suggestion. For consistency with normal tuple specialization, I would declare them as hidden friends. This mean putting them inside tuple<> specialization and declaring as:: + 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; } // We do not need tuple<>, as we can use injected class names. I have realized that we are also missing the _UTuple&& constructors and assignment, if this is something you are interested in adding, please feel free to submit a separate patch. What we need are following, with additional requirements on being tuple_like and size 0 tuple(_UTuple&&) -> !is_same_v<remove_cvref_t<_UTuple, tuple>> && !is_same_v<remove_cvref_t<_UTuple>, allocator_arg_t> tuple(allocator_arg_t, _UTuple&&) -> !is_same_v<remove_cvref_t<_UTuple, tuple>> tuple& operator=(_UTuple&&); -> !is_same_v<remove_cvref_t<_UTuple, tuple>> tuple const& operator=(_UTuple&&) const; -> !is_same_v<remove_cvref_t<_UTuple, tuple>> And also const copy-assigment matching swap: tuple const& operator=(tuple const&) const; And if we add above we need to default usual copy assignment: tuple& operator=(tuple const&) = default; + > #if !(__cpp_concepts && __cpp_consteval && __cpp_conditional_explicit) // > !C++20 > /// Partial specialization, 2-element tuple. > /// Includes construction and assignment from a pair. > diff --git > a/libstdc++-v3/testsuite/23_containers/tuple/comparison_operators/119721.cc > b/libstdc++-v3/testsuite/23_containers/tuple/comparison_operators/119721.cc > new file mode 100644 > index 000000000..711874acf > --- /dev/null > +++ > b/libstdc++-v3/testsuite/23_containers/tuple/comparison_operators/119721.cc > @@ -0,0 +1,71 @@ > +// { dg-do compile { target c++23 } } > +// { dg-options "-std=c++23" } > + > +// Test for PR libstdc++/119721: tuple<> comparison with array<T, 0> > + > +#include <tuple> > +#include <array> > +#include <cassert> > + > +void test01() > +{ > + std::tuple<> t; > + std::array<int, 0> a; > + > + // Basic comparison should work > + assert(t == a); > + assert(a == t); > + assert(!(t != a)); > + assert(!(a != t)); > + > + // Ordering comparisons should be equal > + assert(!(t < a)); > + assert(!(t > a)); > + assert(t <= a); > + assert(t >= a); > + assert(!(a < t)); > + assert(!(a > t)); > + assert(a <= t); > + assert(a >= t); > + > + // Three-way comparison should return equal > + assert((t <=> a) == std::strong_ordering::equal); > + assert((a <=> t) == std::strong_ordering::equal); > +} > + > +void test02() > +{ > + // Test with non-comparable element type > + struct NonComparable { > + void operator==(const NonComparable&) const = delete; > + void operator<=>(const NonComparable&) const = delete; > + }; > + > + std::tuple<> t; > + std::array<NonComparable, 0> a; > + > + // Should still work because empty containers don't compare elements > + assert(t == a); > + assert((t <=> a) == std::strong_ordering::equal); > +} > + > +void test03() > +{ > + // Test constexpr evaluation > + constexpr std::tuple<> t; > + constexpr std::array<int, 0> a; > + > + constexpr bool eq = t == a; > + constexpr auto cmp = t <=> a; > + > + static_assert(eq == true); > + static_assert(cmp == std::strong_ordering::equal); > +} > + > +int main() > +{ > + test01(); > + test02(); > + test03(); > + return 0; > +} > -- > 2.43.0 > >
