Commit b80f2dfd authored by Kent Ross's avatar Kent Ross
Browse files

[libc++][spaceship] Implement std::tuple::operator<=>

Implement parts of P1614, including three-way comparison for tuples, and expand testing.

Reviewed By: ldionne, Mordante, #libc

Differential Revision: https://reviews.llvm.org/D108250
parent 955dc344
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@ Section,Description,Dependencies,Assignee,Complete
| `[syserr.errcat.nonvirtuals] <https://wg21.link/syserr.errcat.nonvirtuals>`_,| error_category,[comparisons.three.way],Unassigned,|Not Started|
| `[syserr.compare] <https://wg21.link/syserr.compare>`_,"| error_code
| error_condition",None,Unassigned,|Not Started|
| `[tuple.rel] <https://wg21.link/tuple.rel>`_,| `tuple <https://reviews.llvm.org/D108250>`_,[expos.only.func],Kent Ross,|In Progress|
| `[tuple.rel] <https://wg21.link/tuple.rel>`_,| `tuple <https://reviews.llvm.org/D108250>`_,[expos.only.func],Kent Ross,|Complete|
"| `[optional.relops] <https://wg21.link/optional.relops>`_
| `[optional.nullops] <https://wg21.link/optional.nullops>`_
| `[optional.comp.with.t] <https://wg21.link/optional.comp.with.t>`_","| optional
+37 −5
Original line number Diff line number Diff line
@@ -132,11 +132,14 @@ template <class T1, class... T>

// 20.4.1.6, relational operators:
template<class... T, class... U> bool operator==(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14
template<class... T, class... U> bool operator<(const tuple<T...>&, const tuple<U...>&);  // constexpr in C++14
template<class... T, class... U> bool operator!=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14
template<class... T, class... U> bool operator>(const tuple<T...>&, const tuple<U...>&);  // constexpr in C++14
template<class... T, class... U> bool operator<=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14
template<class... T, class... U> bool operator>=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14
template<class... T, class... U> bool operator<(const tuple<T...>&, const tuple<U...>&);  // constexpr in C++14, removed in C++20
template<class... T, class... U> bool operator!=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14, removed in C++20
template<class... T, class... U> bool operator>(const tuple<T...>&, const tuple<U...>&);  // constexpr in C++14, removed in C++20
template<class... T, class... U> bool operator<=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14, removed in C++20
template<class... T, class... U> bool operator>=(const tuple<T...>&, const tuple<U...>&); // constexpr in C++14, removed in C++20
template<class... T, class... U>
  constexpr common_comparison_category_t<synth-three-way-result<T, U>...>
    operator<=>(const tuple<T...>&, const tuple<U...>&);                                  // since C++20

template <class... Types, class Alloc>
  struct uses_allocator<tuple<Types...>, Alloc>;
@@ -149,6 +152,8 @@ template <class... Types>

*/

#include <__compare/common_comparison_category.h>
#include <__compare/synth_three_way.h>
#include <__config>
#include <__functional/unwrap_ref.h>
#include <__functional_base>
@@ -156,6 +161,7 @@ template <class... Types>
#include <__memory/uses_allocator.h>
#include <__tuple>
#include <__utility/forward.h>
#include <__utility/integer_sequence.h>
#include <__utility/move.h>
#include <compare>
#include <cstddef>
@@ -1300,6 +1306,30 @@ operator==(const tuple<_Tp...>& __x, const tuple<_Up...>& __y)
    return __tuple_equal<sizeof...(_Tp)>()(__x, __y);
}

#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_SPACESHIP_OPERATOR) && !defined(_LIBCPP_HAS_NO_CONCEPTS)

// operator<=>

template <class ..._Tp, class ..._Up, size_t ..._Is>
_LIBCPP_HIDE_FROM_ABI constexpr
auto
__tuple_compare_three_way(const tuple<_Tp...>& __x, const tuple<_Up...>& __y, index_sequence<_Is...>) {
    common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...> __result = strong_ordering::equal;
    static_cast<void>(((__result = _VSTD::__synth_three_way(_VSTD::get<_Is>(__x), _VSTD::get<_Is>(__y)), __result != 0) || ...));
    return __result;
}

template <class ..._Tp, class ..._Up>
requires (sizeof...(_Tp) == sizeof...(_Up))
_LIBCPP_HIDE_FROM_ABI constexpr
common_comparison_category_t<__synth_three_way_result<_Tp, _Up>...>
operator<=>(const tuple<_Tp...>& __x, const tuple<_Up...>& __y)
{
    return _VSTD::__tuple_compare_three_way(__x, __y, index_sequence_for<_Tp...>{});
}

#else // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_SPACESHIP_OPERATOR) && !defined(_LIBCPP_HAS_NO_CONCEPTS)

template <class ..._Tp, class ..._Up>
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR_AFTER_CXX11
bool
@@ -1368,6 +1398,8 @@ operator<=(const tuple<_Tp...>& __x, const tuple<_Up...>& __y)
    return !(__y < __x);
}

#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_SPACESHIP_OPERATOR) && !defined(_LIBCPP_HAS_NO_CONCEPTS)

// tuple_cat

template <class _Tp, class _Up> struct __tuple_cat_type;
+179 −0
Original line number Diff line number Diff line
@@ -9,16 +9,27 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, libcpp-no-concepts
// ADDITIONAL_COMPILE_FLAGS: -Wno-sign-compare

// constexpr auto __synth_three_way = ...;
// constexpr auto synth-three-way = ...;
//   via std::tuple<T>(t) <=> std::tuple<U>(u), which exposes its behavior most directly

#include <cassert>
#include <compare>
#include <limits>
#include <utility> // Includes synth-three-way via std::pair::operator<=>
#include <limits>  // quiet_NaN
#include <tuple>
#include <utility> // declval

#include "test_macros.h"

template <typename T> concept can_synth_three_way = requires(T t) { std::__synth_three_way(t, t); };
template <typename T, typename U = T>
concept can_synth_three_way = requires(T t, U u) { std::tuple<T>(t) <=> std::tuple<U>(u); };

template <typename T, typename U>
constexpr auto synth_three_way(const T& t, const U& u) {
  return std::tuple<T>(t) <=> std::tuple<U>(u);
}

template <typename T, typename U>
using synth_three_way_result = decltype(std::declval<std::tuple<T>>() <=> std::declval<std::tuple<U>>());

// A custom three-way result type
struct CustomEquality {
@@ -29,22 +40,22 @@ struct CustomEquality {

constexpr bool test() {
  {
    assert(std::__synth_three_way(1, 1) == std::strong_ordering::equal);
    assert(std::__synth_three_way(2, 1) == std::strong_ordering::greater);
    assert(std::__synth_three_way(1, 2) == std::strong_ordering::less);
    ASSERT_SAME_TYPE(std::strong_ordering, std::__synth_three_way_result<int, int>);
    ASSERT_SAME_TYPE(std::strong_ordering, std::__synth_three_way_result<short, long long int>);
    assert(synth_three_way(1, 1) == std::strong_ordering::equal);
    assert(synth_three_way(2, 1) == std::strong_ordering::greater);
    assert(synth_three_way(1, 2) == std::strong_ordering::less);
    ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<int, int>);
    ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<short, long long int>);
  }
  {
    constexpr double nan = std::numeric_limits<double>::quiet_NaN();
    assert(std::__synth_three_way(1.0, 1.0) == std::partial_ordering::equivalent);
    assert(std::__synth_three_way(2.0, 1.0) == std::partial_ordering::greater);
    assert(std::__synth_three_way(1.0, 2.0) == std::partial_ordering::less);
    assert(std::__synth_three_way(nan, nan) == std::partial_ordering::unordered);
    ASSERT_SAME_TYPE(std::partial_ordering, std::__synth_three_way_result<double, double>);
    ASSERT_SAME_TYPE(std::partial_ordering, std::__synth_three_way_result<double, float>);
    ASSERT_SAME_TYPE(std::partial_ordering, std::__synth_three_way_result<double, int>);
    ASSERT_SAME_TYPE(std::partial_ordering, std::__synth_three_way_result<float, short>);
    assert(synth_three_way(1.0, 1.0) == std::partial_ordering::equivalent);
    assert(synth_three_way(2.0, 1.0) == std::partial_ordering::greater);
    assert(synth_three_way(1.0, 2.0) == std::partial_ordering::less);
    assert(synth_three_way(nan, nan) == std::partial_ordering::unordered);
    ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<double, double>);
    ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<double, float>);
    ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<double, int>);
    ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<float, short>);
  }
  {
    struct StrongSpaceship {
@@ -52,10 +63,10 @@ constexpr bool test() {
      constexpr bool operator==(const StrongSpaceship&) const = default;
      constexpr std::strong_ordering operator<=>(const StrongSpaceship& other) const { return value <=> other.value; }
    };
    assert(std::__synth_three_way(StrongSpaceship{1}, StrongSpaceship{1}) == std::strong_ordering::equal);
    assert(std::__synth_three_way(StrongSpaceship{2}, StrongSpaceship{1}) == std::strong_ordering::greater);
    assert(std::__synth_three_way(StrongSpaceship{1}, StrongSpaceship{2}) == std::strong_ordering::less);
    ASSERT_SAME_TYPE(std::strong_ordering, std::__synth_three_way_result<StrongSpaceship, StrongSpaceship>);
    assert(synth_three_way(StrongSpaceship{1}, StrongSpaceship{1}) == std::strong_ordering::equal);
    assert(synth_three_way(StrongSpaceship{2}, StrongSpaceship{1}) == std::strong_ordering::greater);
    assert(synth_three_way(StrongSpaceship{1}, StrongSpaceship{2}) == std::strong_ordering::less);
    ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<StrongSpaceship, StrongSpaceship>);
  }
  {
    struct WeakSpaceship {
@@ -65,10 +76,10 @@ constexpr bool test() {
        return value <=> other.value;
      }
    };
    assert(std::__synth_three_way(WeakSpaceship{1}, WeakSpaceship{1}) == std::weak_ordering::equivalent);
    assert(std::__synth_three_way(WeakSpaceship{2}, WeakSpaceship{1}) == std::weak_ordering::greater);
    assert(std::__synth_three_way(WeakSpaceship{1}, WeakSpaceship{2}) == std::weak_ordering::less);
    ASSERT_SAME_TYPE(std::weak_ordering, std::__synth_three_way_result<WeakSpaceship, WeakSpaceship>);
    assert(synth_three_way(WeakSpaceship{1}, WeakSpaceship{1}) == std::weak_ordering::equivalent);
    assert(synth_three_way(WeakSpaceship{2}, WeakSpaceship{1}) == std::weak_ordering::greater);
    assert(synth_three_way(WeakSpaceship{1}, WeakSpaceship{2}) == std::weak_ordering::less);
    ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<WeakSpaceship, WeakSpaceship>);
  }
  {
    struct PartialSpaceship {
@@ -79,11 +90,11 @@ constexpr bool test() {
      }
    };
    constexpr double nan = std::numeric_limits<double>::quiet_NaN();
    assert(std::__synth_three_way(PartialSpaceship{1.0}, PartialSpaceship{1.0}) == std::partial_ordering::equivalent);
    assert(std::__synth_three_way(PartialSpaceship{2.0}, PartialSpaceship{1.0}) == std::partial_ordering::greater);
    assert(std::__synth_three_way(PartialSpaceship{1.0}, PartialSpaceship{2.0}) == std::partial_ordering::less);
    assert(std::__synth_three_way(PartialSpaceship{nan}, PartialSpaceship{nan}) == std::partial_ordering::unordered);
    ASSERT_SAME_TYPE(std::partial_ordering, std::__synth_three_way_result<PartialSpaceship, PartialSpaceship>);
    assert(synth_three_way(PartialSpaceship{1.0}, PartialSpaceship{1.0}) == std::partial_ordering::equivalent);
    assert(synth_three_way(PartialSpaceship{2.0}, PartialSpaceship{1.0}) == std::partial_ordering::greater);
    assert(synth_three_way(PartialSpaceship{1.0}, PartialSpaceship{2.0}) == std::partial_ordering::less);
    assert(synth_three_way(PartialSpaceship{nan}, PartialSpaceship{nan}) == std::partial_ordering::unordered);
    ASSERT_SAME_TYPE(std::partial_ordering, synth_three_way_result<PartialSpaceship, PartialSpaceship>);
  }
  {
    struct NoSpaceship {
@@ -91,10 +102,10 @@ constexpr bool test() {
      constexpr bool operator==(const NoSpaceship&) const = default;
      constexpr bool operator<(const NoSpaceship& other) const { return value < other.value; }
    };
    assert(std::__synth_three_way(NoSpaceship{1}, NoSpaceship{1}) == std::weak_ordering::equivalent);
    assert(std::__synth_three_way(NoSpaceship{2}, NoSpaceship{1}) == std::weak_ordering::greater);
    assert(std::__synth_three_way(NoSpaceship{1}, NoSpaceship{2}) == std::weak_ordering::less);
    ASSERT_SAME_TYPE(std::weak_ordering, std::__synth_three_way_result<NoSpaceship, NoSpaceship>);
    assert(synth_three_way(NoSpaceship{1}, NoSpaceship{1}) == std::weak_ordering::equivalent);
    assert(synth_three_way(NoSpaceship{2}, NoSpaceship{1}) == std::weak_ordering::greater);
    assert(synth_three_way(NoSpaceship{1}, NoSpaceship{2}) == std::weak_ordering::less);
    ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<NoSpaceship, NoSpaceship>);
  }
  {
    // Types with operator<=> but no operator== are not three_way_comparable and will fall back to operator< and
@@ -104,8 +115,8 @@ constexpr bool test() {
        return std::strong_ordering::equivalent;
      }
    };
    assert(std::__synth_three_way(SpaceshipNoEquals{}, SpaceshipNoEquals{}) == std::weak_ordering::equivalent);
    ASSERT_SAME_TYPE(std::weak_ordering, std::__synth_three_way_result<SpaceshipNoEquals, SpaceshipNoEquals>);
    assert(synth_three_way(SpaceshipNoEquals{}, SpaceshipNoEquals{}) == std::weak_ordering::equivalent);
    ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<SpaceshipNoEquals, SpaceshipNoEquals>);
  }
  {
    // Custom three-way-comparison result types cannot satisfy standard concepts (and therefore synth-three-way)
@@ -115,10 +126,10 @@ constexpr bool test() {
    struct CustomSpaceship {
      constexpr CustomEquality operator<=>(const CustomSpaceship&) const { return CustomEquality(); }
    };
    assert((CustomSpaceship() <=> CustomSpaceship()) == 0);
    assert(!(CustomSpaceship() < CustomSpaceship()));
    assert(std::__synth_three_way(CustomSpaceship(), CustomSpaceship()) == std::weak_ordering::equivalent);
    ASSERT_SAME_TYPE(std::weak_ordering, std::__synth_three_way_result<CustomSpaceship, CustomSpaceship>);
    assert((CustomSpaceship{} <=> CustomSpaceship{}) == 0);
    assert(!(CustomSpaceship{} < CustomSpaceship{}));
    assert(synth_three_way(CustomSpaceship{}, CustomSpaceship{}) == std::weak_ordering::equivalent);
    ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<CustomSpaceship, CustomSpaceship>);
  }
  // SFINAE tests demonstrating synth-three-way needs three_way_comparable or operator<.
  {
@@ -137,17 +148,24 @@ constexpr bool test() {
    static_assert(!can_synth_three_way<NoLessThan>);
  }
  {
    assert(std::__synth_three_way(1, 1U) == std::weak_ordering::equivalent);
    assert(std::__synth_three_way(-1, 0U) == std::weak_ordering::greater);
    assert(synth_three_way(1, 1U) == std::weak_ordering::equivalent);
    assert(synth_three_way(-1, 0U) == std::weak_ordering::greater);
    // Even with the warning suppressed (-Wno-sign-compare) there should still be no <=> operator
    // between signed and unsigned types, so we should end up with a synthesized weak ordering.
    ASSERT_SAME_TYPE(std::weak_ordering, std::__synth_three_way_result<int, unsigned int>);
    ASSERT_SAME_TYPE(std::weak_ordering, synth_three_way_result<int, unsigned int>);
    // When an unsigned type can be narrowed to a larger signed type, <=> should be defined and we
    // should get a strong ordering. (This probably does not raise a warning due to safe narrowing.)
    assert((static_cast<long long int>(-1) <=> static_cast<unsigned char>(0)) == std::strong_ordering::less);
    assert(std::__synth_three_way(static_cast<long long int>(-1),
                                  static_cast<unsigned char>(0)) == std::strong_ordering::less);
    ASSERT_SAME_TYPE(std::strong_ordering, std::__synth_three_way_result<long long int, unsigned char>);
    assert(synth_three_way(static_cast<long long int>(-1), static_cast<unsigned char>(0)) == std::strong_ordering::less);
    assert(synth_three_way(static_cast<long long int>(-1), static_cast<unsigned char>(0)) == std::strong_ordering::less);
    ASSERT_SAME_TYPE(std::strong_ordering, synth_three_way_result<long long int, unsigned char>);
  }
#ifdef TEST_COMPILER_GCC
  // GCC cannot evaluate NaN @ non-NaN constexpr, so test that runtime-only.
  if (!std::is_constant_evaluated())
#endif
  {
    constexpr double nan = std::numeric_limits<double>::quiet_NaN();
    assert(synth_three_way(nan, 1.0) == std::partial_ordering::unordered);
  }

  return true;
@@ -157,10 +175,5 @@ int main(int, char**) {
  test();
  static_assert(test());

  {
    constexpr double nan = std::numeric_limits<double>::quiet_NaN();
    assert(std::__synth_three_way(nan, 1.0) == std::partial_ordering::unordered);
  }

  return 0;
}
+29 −0
Original line number Diff line number Diff line
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// <tuple>

// template <class... Types> class tuple;

// template<class... TTypes, class... UTypes>
//   bool
//   operator==(const tuple<TTypes...>& t, const tuple<UTypes...>& u);
// template<class... TTypes, class... UTypes>
//   bool
//   operator<(const tuple<TTypes...>& t, const tuple<UTypes...>& u);

// UNSUPPORTED: c++03

#include <tuple>

void f(std::tuple<int> t1, std::tuple<int, long> t2) {
  // We test only the core comparison operators and trust that the others
  // fall back on the same implementations prior to C++20.
  static_cast<void>(t1 == t2); // expected-error@*:* {{}}
  static_cast<void>(t1 < t2); // expected-error@*:* {{}}
}
+28 −0
Original line number Diff line number Diff line
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// <tuple>

// template <class... Types> class tuple;

// template<class... TTypes, class... UTypes>
//   auto
//   operator<=>(const tuple<TTypes...>& t, const tuple<UTypes...>& u);

// UNSUPPORTED: c++03, c++11, c++14, c++17, libcpp-no-concepts

#include <tuple>

template <class T, class U>
concept can_compare = requires(T t, U u) { t <=> u; };

typedef std::tuple<int> T1;
typedef std::tuple<int, long> T2;

static_assert(!can_compare<T1, T2>);
static_assert(!can_compare<T2, T1>);
Loading