[libc++] [P1614] Implement std::compare_three_way.

Differential Revision: https://reviews.llvm.org/D110735

GitOrigin-RevId: 3df094d31eac753687c7963738bb847e6d3794e9
diff --git a/docs/Status/SpaceshipProjects.csv b/docs/Status/SpaceshipProjects.csv
index f517e2b..e68f525 100644
--- a/docs/Status/SpaceshipProjects.csv
+++ b/docs/Status/SpaceshipProjects.csv
@@ -4,7 +4,7 @@
 | `[cmp.result] <https://wg21.link/cmp.result>`_,| `compare_three_way_result <https://reviews.llvm.org/D103581>`_,None,Arthur O'Dwyer,|Complete|
 | `[expos.only.func] <https://wg21.link/expos.only.func>`_,"| `synth-three-way <https://reviews.llvm.org/D107721>`_
 | `synth-three-way-result <https://reviews.llvm.org/D107721>`_",[cmp.concept],Kent Ross,|Complete|
-| `[comparisons.three.way] <https://wg21.link/comparisons.three.way>`_,| `compare_three_way <https://reviews.llvm.org/D80899>`_,[cmp.concept],Christopher Di Bella,|In Progress|
+| `[comparisons.three.way] <https://wg21.link/comparisons.three.way>`_,| `compare_three_way <https://reviews.llvm.org/D80899>`_,[cmp.concept],Arthur O'Dwyer,|Complete|
 | `[cmp.alg] <https://wg21.link/cmp.alg>`_,"| `strong_order <https://reviews.llvm.org/D107036>`_
 | `weak_order <https://reviews.llvm.org/D107036>`_
 | `partial_order <https://reviews.llvm.org/D107036>`_",None,Arthur O'Dwyer,|In Progress|
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index 3460f72..55834ab 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -102,6 +102,7 @@
   __charconv/from_chars_result.h
   __charconv/to_chars_result.h
   __compare/common_comparison_category.h
+  __compare/compare_three_way.h
   __compare/compare_three_way_result.h
   __compare/is_eq.h
   __compare/ordering.h
diff --git a/include/__compare/compare_three_way.h b/include/__compare/compare_three_way.h
new file mode 100644
index 0000000..492908a
--- /dev/null
+++ b/include/__compare/compare_three_way.h
@@ -0,0 +1,41 @@
+// -*- C++ -*-
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___COMPARE_COMPARE_THREE_WAY_H
+#define _LIBCPP___COMPARE_COMPARE_THREE_WAY_H
+
+#include <__config>
+#include <__compare/three_way_comparable.h>
+#include <__utility/forward.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_SPACESHIP_OPERATOR) && !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+struct _LIBCPP_TEMPLATE_VIS compare_three_way
+{
+    template<class _T1, class _T2>
+        requires three_way_comparable_with<_T1, _T2>
+    constexpr _LIBCPP_HIDE_FROM_ABI
+    auto operator()(_T1&& __t, _T2&& __u) const
+        noexcept(noexcept(_VSTD::forward<_T1>(__t) <=> _VSTD::forward<_T2>(__u)))
+        { return          _VSTD::forward<_T1>(__t) <=> _VSTD::forward<_T2>(__u); }
+
+    using is_transparent = void;
+};
+
+#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_SPACESHIP_OPERATOR) && !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___COMPARE_COMPARE_THREE_WAY_H
diff --git a/include/compare b/include/compare
index ca1f574..ddb8313 100644
--- a/include/compare
+++ b/include/compare
@@ -47,6 +47,9 @@
   template<class T, class U = T>
     using compare_three_way_result_t = typename compare_three_way_result<T, U>::type;
 
+  // [comparisons.three.way], class compare_three_way
+  struct compare_three_way; // C++20
+
   // [cmp.alg], comparison algorithms
   template<class T> constexpr strong_ordering strong_order(const T& a, const T& b);
   template<class T> constexpr weak_ordering weak_order(const T& a, const T& b);
@@ -133,6 +136,7 @@
 */
 
 #include <__compare/common_comparison_category.h>
+#include <__compare/compare_three_way.h>
 #include <__compare/compare_three_way_result.h>
 #include <__compare/is_eq.h>
 #include <__compare/ordering.h>
diff --git a/include/functional b/include/functional
index 3cff866..53a5f2b 100644
--- a/include/functional
+++ b/include/functional
@@ -135,6 +135,9 @@
     bool operator()(const T& x, const T& y) const;
 };
 
+// [comparisons.three.way], class compare_three_way
+struct compare_three_way;
+
 template <class T> // <class T=void> in C++14
 struct logical_and {
     bool operator()(const T& x, const T& y) const;
@@ -488,6 +491,7 @@
 */
 
 #include <__algorithm/search.h>
+#include <__compare/compare_three_way.h>
 #include <__config>
 #include <__debug>
 #include <__functional/binary_function.h> // TODO: deprecate
diff --git a/include/module.modulemap b/include/module.modulemap
index 5abe243..d8049cb 100644
--- a/include/module.modulemap
+++ b/include/module.modulemap
@@ -372,6 +372,7 @@
 
     module __compare {
       module common_comparison_category { private header "__compare/common_comparison_category.h" }
+      module compare_three_way          { private header "__compare/compare_three_way.h"          }
       module compare_three_way_result   { private header "__compare/compare_three_way_result.h"   }
       module is_eq                      { private header "__compare/is_eq.h"                      }
       module ordering                   { private header "__compare/ordering.h"                   }
diff --git a/test/libcxx/diagnostics/detail.headers/compare/compare_three_way.module.verify.cpp b/test/libcxx/diagnostics/detail.headers/compare/compare_three_way.module.verify.cpp
new file mode 100644
index 0000000..e6428ce
--- /dev/null
+++ b/test/libcxx/diagnostics/detail.headers/compare/compare_three_way.module.verify.cpp
@@ -0,0 +1,15 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: modules-build
+
+// WARNING: This test was generated by 'generate_private_header_tests.py'
+// and should not be edited manually.
+
+// expected-error@*:* {{use of private header from outside its module: '__compare/compare_three_way.h'}}
+#include <__compare/compare_three_way.h>
diff --git a/test/std/utilities/function.objects/comparisons/compare_three_way.pass.cpp b/test/std/utilities/function.objects/comparisons/compare_three_way.pass.cpp
new file mode 100644
index 0000000..63e0eb3
--- /dev/null
+++ b/test/std/utilities/function.objects/comparisons/compare_three_way.pass.cpp
@@ -0,0 +1,83 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-no-concepts
+
+// <compare>
+// <functional>
+
+// compare_three_way
+
+#include <compare>
+#include <cassert>
+#include <limits>
+#include <type_traits>
+
+#include "pointer_comparison_test_helper.h"
+
+template<class T, class U>
+constexpr auto test_sfinae(T t, U u)
+    -> decltype(std::compare_three_way()(t, u), std::true_type{})
+    { return std::true_type{}; }
+
+constexpr auto test_sfinae(...)
+    { return std::false_type{}; }
+
+struct NotThreeWayComparable {
+    std::strong_ordering operator<=>(const NotThreeWayComparable&) const;
+};
+ASSERT_SAME_TYPE(std::compare_three_way_result_t<NotThreeWayComparable>, std::strong_ordering);
+static_assert(!std::three_way_comparable<NotThreeWayComparable>);  // it lacks operator==
+
+struct WeaklyOrdered {
+    int i;
+    friend constexpr std::weak_ordering operator<=>(const WeaklyOrdered&, const WeaklyOrdered&) = default;
+};
+
+constexpr bool test()
+{
+    ASSERT_SAME_TYPE(decltype(std::compare_three_way()(1, 1)), std::strong_ordering);
+    assert(std::compare_three_way()(1, 2) == std::strong_ordering::less);
+    assert(std::compare_three_way()(1, 1) == std::strong_ordering::equal);
+    assert(std::compare_three_way()(2, 1) == std::strong_ordering::greater);
+
+    ASSERT_SAME_TYPE(decltype(std::compare_three_way()(WeaklyOrdered{1}, WeaklyOrdered{2})), std::weak_ordering);
+    assert(std::compare_three_way()(WeaklyOrdered{1}, WeaklyOrdered{2}) == std::weak_ordering::less);
+    assert(std::compare_three_way()(WeaklyOrdered{1}, WeaklyOrdered{1}) == std::weak_ordering::equivalent);
+    assert(std::compare_three_way()(WeaklyOrdered{2}, WeaklyOrdered{1}) == std::weak_ordering::greater);
+
+    ASSERT_SAME_TYPE(decltype(std::compare_three_way()(1.0, 1.0)), std::partial_ordering);
+    double nan = std::numeric_limits<double>::quiet_NaN();
+    assert(std::compare_three_way()(1.0, 2.0) == std::partial_ordering::less);
+    assert(std::compare_three_way()(1.0, 1.0) == std::partial_ordering::equivalent);
+    assert(std::compare_three_way()(2.0, 1.0) == std::partial_ordering::greater);
+    assert(std::compare_three_way()(nan, nan) == std::partial_ordering::unordered);
+
+    // Try heterogeneous comparison.
+    ASSERT_SAME_TYPE(decltype(std::compare_three_way()(42.0, 42)), std::partial_ordering);
+    assert(std::compare_three_way()(42.0, 42) == std::partial_ordering::equivalent);
+    ASSERT_SAME_TYPE(decltype(std::compare_three_way()(42, 42.0)), std::partial_ordering);
+    assert(std::compare_three_way()(42, 42.0) == std::partial_ordering::equivalent);
+
+    return true;
+}
+
+int main(int, char**)
+{
+    test();
+    static_assert(test());
+
+    do_pointer_comparison_test(std::compare_three_way());
+
+    static_assert(test_sfinae(1, 2));
+    static_assert(!test_sfinae(1, nullptr));
+    static_assert(!test_sfinae(NotThreeWayComparable(), NotThreeWayComparable()));
+
+    return 0;
+}
diff --git a/test/std/utilities/function.objects/comparisons/compare_three_way_functional.pass.cpp b/test/std/utilities/function.objects/comparisons/compare_three_way_functional.pass.cpp
new file mode 100644
index 0000000..bb23244
--- /dev/null
+++ b/test/std/utilities/function.objects/comparisons/compare_three_way_functional.pass.cpp
@@ -0,0 +1,27 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-no-concepts
+
+// <functional>
+
+// Test that std::compare_three_way is defined in <functional>,
+// not only in <compare>.
+
+#include <functional>
+#include <cassert>
+
+int main(int, char**)
+{
+    assert(std::compare_three_way()(1, 2) < 0);
+    assert(std::compare_three_way()(1, 1) == 0);
+    assert(std::compare_three_way()(2, 1) > 0);
+
+    return 0;
+}
diff --git a/test/std/utilities/function.objects/comparisons/transparent_three_way.compile.pass.cpp b/test/std/utilities/function.objects/comparisons/transparent_three_way.compile.pass.cpp
new file mode 100644
index 0000000..c48d35b
--- /dev/null
+++ b/test/std/utilities/function.objects/comparisons/transparent_three_way.compile.pass.cpp
@@ -0,0 +1,19 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-no-concepts
+
+#include <functional>
+
+template<class T>
+concept Transparent = requires {
+    typename T::is_transparent;
+};
+
+static_assert(Transparent<std::compare_three_way>);