On Mon, Oct 20, 2025 at 10:13 AM Tomasz Kaminski <[email protected]>
wrote:

>
>
> 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;
>
Otherwise is_trivially_copy/move_assignagble will become false.

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