[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
This commit is contained in:
Kent Ross 2021-10-08 14:54:28 -07:00
parent 955dc3449a
commit b80f2dfd11
7 changed files with 506 additions and 172 deletions

View File

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

1 Section Description Dependencies Assignee Complete
14 | `[variant.relops] <https://wg21.link/variant.relops>`_ | `[variant.monostate.relops] <https://wg21.link/variant.monostate.relops>`_ | monostate | variant None Kent Ross |In Progress|
15 | `[unique.ptr.special] <https://wg21.link/unique.ptr.special>`_ | unique_ptr [comparisons.three.way] Unassigned |Not Started|
16 | `[util.smartptr.shared.cmp] <https://wg21.link/util.smartptr.shared.cmp>`_ | shared_ptr [comparisons.three.way] Unassigned |Not Started|
17 | `[type.index.members] <https://wg21.link/type.index.members>`_ | type_index None Unassigned |Not Started|
18 | `[stacktrace.entry.cmp] <https://wg21.link/stacktrace.entry.cmp>`_ | stacktrace_entry None Unassigned |Not Started|
19 | `[stacktrace.basic.cmp] <https://wg21.link/stacktrace.basic.cmp>`_ | basic_stacktrace [alg.three.way] Unassigned |Not Started|
20 | `[string.cmp] <https://wg21.link/string.cmp>`_ | `basic_string <https://reviews.llvm.org/D80895>`_ None Christopher Di Bella |In Progress|

View File

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

View File

@ -1,166 +0,0 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17, libcpp-no-concepts
// ADDITIONAL_COMPILE_FLAGS: -Wno-sign-compare
// constexpr auto __synth_three_way = ...;
#include <cassert>
#include <compare>
#include <limits>
#include <utility> // Includes synth-three-way via std::pair::operator<=>
#include "test_macros.h"
template <typename T> concept can_synth_three_way = requires(T t) { std::__synth_three_way(t, t); };
// A custom three-way result type
struct CustomEquality {
friend constexpr bool operator==(const CustomEquality&, int) noexcept { return true; }
friend constexpr bool operator<(const CustomEquality&, int) noexcept { return false; }
friend constexpr bool operator<(int, const CustomEquality&) noexcept { return false; }
};
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>);
}
{
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>);
}
{
struct StrongSpaceship {
int value;
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>);
}
{
struct WeakSpaceship {
int value;
constexpr bool operator==(const WeakSpaceship&) const = default;
constexpr std::weak_ordering operator<=>(const WeakSpaceship& other) const {
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>);
}
{
struct PartialSpaceship {
double value;
constexpr bool operator==(const PartialSpaceship&) const = default;
constexpr std::partial_ordering operator<=>(const PartialSpaceship& other) const {
return value <=> other.value;
}
};
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>);
}
{
struct NoSpaceship {
int value;
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>);
}
{
// Types with operator<=> but no operator== are not three_way_comparable and will fall back to operator< and
// compare as weakly ordered.
struct SpaceshipNoEquals {
constexpr std::strong_ordering operator<=>(const SpaceshipNoEquals&) const {
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>);
}
{
// Custom three-way-comparison result types cannot satisfy standard concepts (and therefore synth-three-way)
// because they are not understood by std::common_comparison_category, but they can still be used in
// the same way as standard orderings to do comparisons, and thus can be used by synth-three-way to yield a
// weakly-ordered result.
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>);
}
// SFINAE tests demonstrating synth-three-way needs three_way_comparable or operator<.
{
struct NoRelative {
constexpr bool operator==(const NoRelative&) const;
};
static_assert(!can_synth_three_way<NoRelative>);
}
{
struct NoLessThan {
constexpr bool operator==(const NoLessThan&) const;
constexpr bool operator>(const NoLessThan&) const;
constexpr bool operator>=(const NoLessThan&) const;
constexpr bool operator<=(const NoLessThan&) const;
};
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);
// 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>);
// 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>);
}
return true;
}
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;
}

View File

@ -0,0 +1,179 @@
//===----------------------------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
// UNSUPPORTED: c++03, c++11, c++14, c++17, libcpp-no-concepts
// ADDITIONAL_COMPILE_FLAGS: -Wno-sign-compare
// 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> // quiet_NaN
#include <tuple>
#include <utility> // declval
#include "test_macros.h"
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 {
friend constexpr bool operator==(const CustomEquality&, int) noexcept { return true; }
friend constexpr bool operator<(const CustomEquality&, int) noexcept { return false; }
friend constexpr bool operator<(int, const CustomEquality&) noexcept { return false; }
};
constexpr bool test() {
{
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(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 {
int value;
constexpr bool operator==(const StrongSpaceship&) const = default;
constexpr std::strong_ordering operator<=>(const StrongSpaceship& other) const { return value <=> other.value; }
};
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 {
int value;
constexpr bool operator==(const WeakSpaceship&) const = default;
constexpr std::weak_ordering operator<=>(const WeakSpaceship& other) const {
return value <=> other.value;
}
};
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 {
double value;
constexpr bool operator==(const PartialSpaceship&) const = default;
constexpr std::partial_ordering operator<=>(const PartialSpaceship& other) const {
return value <=> other.value;
}
};
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
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 {
int value;
constexpr bool operator==(const NoSpaceship&) const = default;
constexpr bool operator<(const NoSpaceship& other) const { return value < other.value; }
};
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
// compare as weakly ordered.
struct SpaceshipNoEquals {
constexpr std::strong_ordering operator<=>(const SpaceshipNoEquals&) const {
return std::strong_ordering::equivalent;
}
};
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)
// because they are not understood by std::common_comparison_category, but they can still be used in
// the same way as standard orderings to do comparisons, and thus can be used by synth-three-way to yield a
// weakly-ordered result.
struct CustomSpaceship {
constexpr CustomEquality operator<=>(const CustomSpaceship&) const { return CustomEquality(); }
};
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<.
{
struct NoRelative {
constexpr bool operator==(const NoRelative&) const;
};
static_assert(!can_synth_three_way<NoRelative>);
}
{
struct NoLessThan {
constexpr bool operator==(const NoLessThan&) const;
constexpr bool operator>(const NoLessThan&) const;
constexpr bool operator>=(const NoLessThan&) const;
constexpr bool operator<=(const NoLessThan&) const;
};
static_assert(!can_synth_three_way<NoLessThan>);
}
{
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, 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(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;
}
int main(int, char**) {
test();
static_assert(test());
return 0;
}

View File

@ -0,0 +1,29 @@
//===----------------------------------------------------------------------===//
//
// 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@*:* {{}}
}

View File

@ -0,0 +1,28 @@
//===----------------------------------------------------------------------===//
//
// 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>);

View File

@ -0,0 +1,232 @@
//===----------------------------------------------------------------------===//
//
// 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
// ADDITIONAL_COMPILE_FLAGS: -Wno-sign-compare
#include <cassert>
#include <compare>
#include <limits>
#include <tuple>
#include <type_traits> // std::is_constant_evaluated
#include "test_macros.h"
// A custom three-way result type
struct CustomEquality {
friend constexpr bool operator==(const CustomEquality&, int) noexcept { return true; }
friend constexpr bool operator<(const CustomEquality&, int) noexcept { return false; }
friend constexpr bool operator<(int, const CustomEquality&) noexcept { return false; }
};
constexpr bool test() {
// Empty tuple
{
typedef std::tuple<> T0;
// No member types yields strong ordering (all are equal).
ASSERT_SAME_TYPE(decltype(T0() <=> T0()), std::strong_ordering);
assert((T0() <=> T0()) == std::strong_ordering::equal);
}
// Mixed types with integers, which compare strongly ordered
{
typedef std::tuple<long> T1;
typedef std::tuple<short> T2;
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::strong_ordering);
assert((T1(1) <=> T2(1)) == std::strong_ordering::equal);
assert((T1(1) <=> T2(0)) == std::strong_ordering::greater);
assert((T1(1) <=> T2(2)) == std::strong_ordering::less);
}
{
typedef std::tuple<long, unsigned int> T1;
typedef std::tuple<short, unsigned long> T2;
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::strong_ordering);
assert((T1(1, 2) <=> T2(1, 2)) == std::strong_ordering::equal);
assert((T1(1, 2) <=> T2(0, 2)) == std::strong_ordering::greater);
assert((T1(1, 2) <=> T2(2, 2)) == std::strong_ordering::less);
assert((T1(1, 2) <=> T2(1, 1)) == std::strong_ordering::greater);
assert((T1(1, 2) <=> T2(1, 3)) == std::strong_ordering::less);
}
{
typedef std::tuple<long, int, unsigned short> T1;
typedef std::tuple<short, long, unsigned int> T2;
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::strong_ordering);
assert((T1(1, 2, 3) <=> T2(1, 2, 3)) == std::strong_ordering::equal);
assert((T1(1, 2, 3) <=> T2(0, 2, 3)) == std::strong_ordering::greater);
assert((T1(1, 2, 3) <=> T2(2, 2, 3)) == std::strong_ordering::less);
assert((T1(1, 2, 3) <=> T2(1, 1, 3)) == std::strong_ordering::greater);
assert((T1(1, 2, 3) <=> T2(1, 3, 3)) == std::strong_ordering::less);
assert((T1(1, 2, 3) <=> T2(1, 2, 2)) == std::strong_ordering::greater);
assert((T1(1, 2, 3) <=> T2(1, 2, 4)) == std::strong_ordering::less);
}
// Mixed types with floating point, which compare partially ordered
{
typedef std::tuple<long> T1;
typedef std::tuple<double> T2;
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
assert((T1(1) <=> T2(1)) == std::partial_ordering::equivalent);
assert((T1(1) <=> T2(0.9)) == std::partial_ordering::greater);
assert((T1(1) <=> T2(1.1)) == std::partial_ordering::less);
}
{
typedef std::tuple<long, float> T1;
typedef std::tuple<double, unsigned int> T2;
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
assert((T1(1, 2) <=> T2(1, 2)) == std::partial_ordering::equivalent);
assert((T1(1, 2) <=> T2(0.9, 2)) == std::partial_ordering::greater);
assert((T1(1, 2) <=> T2(1.1, 2)) == std::partial_ordering::less);
assert((T1(1, 2) <=> T2(1, 1)) == std::partial_ordering::greater);
assert((T1(1, 2) <=> T2(1, 3)) == std::partial_ordering::less);
}
{
typedef std::tuple<short, float, double> T1;
typedef std::tuple<double, long, unsigned int> T2;
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
assert((T1(1, 2, 3) <=> T2(1, 2, 3)) == std::partial_ordering::equivalent);
assert((T1(1, 2, 3) <=> T2(0.9, 2, 3)) == std::partial_ordering::greater);
assert((T1(1, 2, 3) <=> T2(1.1, 2, 3)) == std::partial_ordering::less);
assert((T1(1, 2, 3) <=> T2(1, 1, 3)) == std::partial_ordering::greater);
assert((T1(1, 2, 3) <=> T2(1, 3, 3)) == std::partial_ordering::less);
assert((T1(1, 2, 3) <=> T2(1, 2, 2)) == std::partial_ordering::greater);
assert((T1(1, 2, 3) <=> T2(1, 2, 4)) == std::partial_ordering::less);
}
{
typedef std::tuple<float> T1;
typedef std::tuple<double> T2;
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
// Comparisons with NaN and non-NaN are non-constexpr in GCC, so both sides must be NaN
assert((T1(nan) <=> T2(nan)) == std::partial_ordering::unordered);
}
{
typedef std::tuple<double, double> T1;
typedef std::tuple<float, float> T2;
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
assert((T1(nan, 2) <=> T2(nan, 2)) == std::partial_ordering::unordered);
assert((T1(1, nan) <=> T2(1, nan)) == std::partial_ordering::unordered);
}
{
typedef std::tuple<double, float, float> T1;
typedef std::tuple<double, double, float> T2;
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
assert((T1(nan, 2, 3) <=> T2(nan, 2, 3)) == std::partial_ordering::unordered);
assert((T1(1, nan, 3) <=> T2(1, nan, 3)) == std::partial_ordering::unordered);
assert((T1(1, 2, nan) <=> T2(1, 2, nan)) == std::partial_ordering::unordered);
}
// Ordering classes and synthesized three way comparison
{
typedef std::tuple<long, int, unsigned int> T1;
typedef std::tuple<int, long, unsigned short> T2;
// All strongly ordered members yields strong ordering.
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::strong_ordering);
}
{
struct WeakSpaceship {
constexpr bool operator==(const WeakSpaceship&) const { return true; }
constexpr std::weak_ordering operator<=>(const WeakSpaceship&) const { return std::weak_ordering::equivalent; }
};
{
typedef std::tuple<int, unsigned int, WeakSpaceship> T1;
typedef std::tuple<int, unsigned long, WeakSpaceship> T2;
// Strongly ordered members and a weakly ordered member yields weak ordering.
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::weak_ordering);
}
{
typedef std::tuple<unsigned int, int, WeakSpaceship> T1;
typedef std::tuple<double, long, WeakSpaceship> T2;
// Doubles are partially ordered, so one partial, one strong, and one weak ordering
// yields partial ordering.
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
}
}
{
struct NoSpaceship {
constexpr bool operator==(const NoSpaceship&) const { return true; }
constexpr bool operator<(const NoSpaceship&) const { return false; }
};
typedef std::tuple<int, unsigned int, NoSpaceship> T1;
typedef std::tuple<int, unsigned long, NoSpaceship> T2;
// Strongly ordered members and a weakly ordered member (synthesized) yields weak ordering.
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::weak_ordering);
}
{
struct SpaceshipNoEquals {
constexpr std::strong_ordering operator<=>(const SpaceshipNoEquals&) const { return std::strong_ordering::equal; }
constexpr bool operator<(const SpaceshipNoEquals&) const { return false; }
};
typedef std::tuple<int, unsigned int, SpaceshipNoEquals> T1;
typedef std::tuple<int, unsigned long, SpaceshipNoEquals> T2;
// Spaceship operator with no == operator falls back on the < operator and weak ordering.
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::weak_ordering);
}
{
struct CustomSpaceship {
constexpr CustomEquality operator<=>(const CustomSpaceship&) const { return CustomEquality(); }
};
typedef std::tuple<int, unsigned int, CustomSpaceship> T1;
typedef std::tuple<short, unsigned long, CustomSpaceship> T2;
typedef std::tuple<CustomSpaceship> T3;
// Custom three way return types cannot be used in synthesized three way comparison,
// but they can be used for (rewritten) operator< when synthesizing a weak ordering.
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::weak_ordering);
ASSERT_SAME_TYPE(decltype(T3() <=> T3()), std::weak_ordering);
}
{
typedef std::tuple<long, int> T1;
typedef std::tuple<long, unsigned int> T2;
// 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(decltype(T1() <=> T2()), std::weak_ordering);
}
#ifdef TEST_COMPILER_GCC
// GCC cannot evaluate NaN @ non-NaN constexpr, so test that runtime-only.
if (!std::is_constant_evaluated())
#endif
{
{
typedef std::tuple<double> T1;
typedef std::tuple<int> T2;
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
assert((T1(nan) <=> T2(1)) == std::partial_ordering::unordered);
}
{
typedef std::tuple<double, double> T1;
typedef std::tuple<int, int> T2;
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
assert((T1(nan, 2) <=> T2(1, 2)) == std::partial_ordering::unordered);
assert((T1(1, nan) <=> T2(1, 2)) == std::partial_ordering::unordered);
}
{
typedef std::tuple<double, double, double> T1;
typedef std::tuple<int, int, int> T2;
constexpr double nan = std::numeric_limits<double>::quiet_NaN();
ASSERT_SAME_TYPE(decltype(T1() <=> T2()), std::partial_ordering);
assert((T1(nan, 2, 3) <=> T2(1, 2, 3)) == std::partial_ordering::unordered);
assert((T1(1, nan, 3) <=> T2(1, 2, 3)) == std::partial_ordering::unordered);
assert((T1(1, 2, nan) <=> T2(1, 2, 3)) == std::partial_ordering::unordered);
}
}
return true;
}
int main(int, char**) {
test();
static_assert(test());
return 0;
}