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

Reply via email to