[libc++] Uses operator<=> in string_view

Implements:
- LWG3432 Missing requirement for comparison_category

Implements part of:
- P1614R2 The Mothership has Landed

Reviewed By: #libc, ldionne, jloser, philnik

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

GitOrigin-RevId: 3818b4df1e102a0886138414c2d05fa66f2260cb
diff --git a/docs/Status/Cxx2bIssues.csv b/docs/Status/Cxx2bIssues.csv
index 73365da..d0eaf86 100644
--- a/docs/Status/Cxx2bIssues.csv
+++ b/docs/Status/Cxx2bIssues.csv
@@ -7,7 +7,7 @@
 "`3236 <https://wg21.link/LWG3236>`__","Random access iterator requirements lack limiting relational operators domain to comparing those from the same range","November 2020","|Nothing To Do|","","|ranges|"
 "`3265 <https://wg21.link/LWG3265>`__","``move_iterator``'s conversions are more broken after P1207","November 2020","Fixed by `LWG3435 <https://wg21.link/LWG3435>`__",""
 "`3435 <https://wg21.link/LWG3435>`__","``three_way_comparable_with<reverse_iterator<int*>, reverse_iterator<const int*>>``","November 2020","|Complete|","13.0"
-"`3432 <https://wg21.link/LWG3432>`__","Missing requirement for ``comparison_category``","November 2020","","","|spaceship|"
+"`3432 <https://wg21.link/LWG3432>`__","Missing requirement for ``comparison_category``","November 2020","|Complete|","16.0","|spaceship|"
 "`3447 <https://wg21.link/LWG3447>`__","Deduction guides for ``take_view`` and ``drop_view`` have different constraints","November 2020","|Complete|","14.0"
 "`3450 <https://wg21.link/LWG3450>`__","The const overloads of ``take_while_view::begin/end`` are underconstrained","November 2020","","","|ranges|"
 "`3464 <https://wg21.link/LWG3464>`__","``istream::gcount()`` can overflow","November 2020","",""
diff --git a/docs/Status/SpaceshipProjects.csv b/docs/Status/SpaceshipProjects.csv
index b7d1c56..e0f5e6f 100644
--- a/docs/Status/SpaceshipProjects.csv
+++ b/docs/Status/SpaceshipProjects.csv
@@ -33,7 +33,7 @@
 | `[stacktrace.entry.cmp] <https://wg21.link/stacktrace.entry.cmp>`_,| stacktrace_entry,None,Unassigned,|Not Started|
 | `[stacktrace.basic.cmp] <https://wg21.link/stacktrace.basic.cmp>`_,| basic_stacktrace,[alg.three.way],Unassigned,|Not Started|
 | `[string.cmp] <https://wg21.link/string.cmp>`_,| basic_string,None,Mark de Wever,|In Progress|
-| `[string.view.comparison] <https://wg21.link/string.view.comparison>`_,| `basic_string_view <https://reviews.llvm.org/D130295>`_,None,Mark de Wever,|In Progress|
+| `[string.view.comparison] <https://wg21.link/string.view.comparison>`_,| `basic_string_view <https://reviews.llvm.org/D130295>`_,None,Mark de Wever,|Complete|
 | `[array.syn] <https://wg21.link/array.syn>`_ (`general <https://wg21.link/container.requirements.general#14>`_),| array,[expos.only.func],Unassigned,|Not Started|
 | `[deque.syn] <https://wg21.link/deque.syn>`_ (`general <https://wg21.link/container.requirements.general#14>`_),| deque,[expos.only.func],Unassigned,|Not Started|
 | `[forward.list.syn] <https://wg21.link/forward.list.syn>`_ (`general <https://wg21.link/container.requirements.general#14>`_),| forward_list,[expos.only.func],Unassigned,|Not Started|
diff --git a/include/__compare/ordering.h b/include/__compare/ordering.h
index 1d466d6..ab35a59 100644
--- a/include/__compare/ordering.h
+++ b/include/__compare/ordering.h
@@ -312,6 +312,12 @@
 inline constexpr strong_ordering strong_ordering::equivalent(_OrdResult::__equiv);
 inline constexpr strong_ordering strong_ordering::greater(_OrdResult::__greater);
 
+/// [cmp.categories.pre]/1
+/// The types partial_ordering, weak_ordering, and strong_ordering are
+/// collectively termed the comparison category types.
+template <class _Tp>
+concept __comparison_category = __one_of_v<_Tp, partial_ordering, weak_ordering, strong_ordering>;
+
 #endif // _LIBCPP_STD_VER > 17
 
 _LIBCPP_END_NAMESPACE_STD
diff --git a/include/__string/char_traits.h b/include/__string/char_traits.h
index 18ad67b..7635835 100644
--- a/include/__string/char_traits.h
+++ b/include/__string/char_traits.h
@@ -17,6 +17,7 @@
 #include <__config>
 #include <__functional/hash.h>
 #include <__iterator/iterator_traits.h>
+#include <compare>
 #include <cstdint>
 #include <cstdio>
 #include <cstring>
@@ -193,6 +194,9 @@
     typedef streamoff off_type;
     typedef streampos pos_type;
     typedef mbstate_t state_type;
+#if _LIBCPP_STD_VER > 17
+    using comparison_category = strong_ordering;
+#endif
 
     static inline _LIBCPP_CONSTEXPR_AFTER_CXX14
     void assign(char_type& __c1, const char_type& __c2) _NOEXCEPT {__c1 = __c2;}
@@ -307,6 +311,9 @@
     typedef streamoff off_type;
     typedef streampos pos_type;
     typedef mbstate_t state_type;
+#  if _LIBCPP_STD_VER > 17
+    using comparison_category = strong_ordering;
+#  endif
 
     static inline _LIBCPP_CONSTEXPR_AFTER_CXX14
     void assign(char_type& __c1, const char_type& __c2) _NOEXCEPT {__c1 = __c2;}
@@ -423,6 +430,9 @@
     typedef streamoff      off_type;
     typedef u8streampos    pos_type;
     typedef mbstate_t      state_type;
+#  if _LIBCPP_STD_VER > 17
+    using comparison_category = strong_ordering;
+#  endif
 
     static inline constexpr void assign(char_type& __c1, const char_type& __c2) noexcept
         {__c1 = __c2;}
@@ -524,6 +534,9 @@
     typedef streamoff      off_type;
     typedef u16streampos   pos_type;
     typedef mbstate_t      state_type;
+#if _LIBCPP_STD_VER > 17
+    using comparison_category = strong_ordering;
+#endif
 
     static inline _LIBCPP_CONSTEXPR_AFTER_CXX14
     void assign(char_type& __c1, const char_type& __c2) _NOEXCEPT {__c1 = __c2;}
@@ -615,6 +628,9 @@
     typedef streamoff      off_type;
     typedef u32streampos   pos_type;
     typedef mbstate_t      state_type;
+#if _LIBCPP_STD_VER > 17
+    using comparison_category = strong_ordering;
+#endif
 
     static inline _LIBCPP_CONSTEXPR_AFTER_CXX14
     void assign(char_type& __c1, const char_type& __c2) _NOEXCEPT {__c1 = __c2;}
diff --git a/include/string_view b/include/string_view
index a9aa304..9e954fd 100644
--- a/include/string_view
+++ b/include/string_view
@@ -14,6 +14,8 @@
 
     string_view synopsis
 
+#include <compare>
+
 namespace std {
 
     // 7.2, Class template basic_string_view
@@ -30,21 +32,25 @@
     template<class charT, class traits>
     constexpr bool operator==(basic_string_view<charT, traits> x,
                               basic_string_view<charT, traits> y) noexcept;
-    template<class charT, class traits>
+    template<class charT, class traits>                                                            // Removed in C++20
     constexpr bool operator!=(basic_string_view<charT, traits> x,
                               basic_string_view<charT, traits> y) noexcept;
-    template<class charT, class traits>
+    template<class charT, class traits>                                                            // Removed in C++20
     constexpr bool operator< (basic_string_view<charT, traits> x,
                                  basic_string_view<charT, traits> y) noexcept;
-    template<class charT, class traits>
+    template<class charT, class traits>                                                            // Removed in C++20
     constexpr bool operator> (basic_string_view<charT, traits> x,
                               basic_string_view<charT, traits> y) noexcept;
-    template<class charT, class traits>
+    template<class charT, class traits>                                                            // Removed in C++20
     constexpr bool operator<=(basic_string_view<charT, traits> x,
                                  basic_string_view<charT, traits> y) noexcept;
-    template<class charT, class traits>
+    template<class charT, class traits>                                                            // Removed in C++20
     constexpr bool operator>=(basic_string_view<charT, traits> x,
                               basic_string_view<charT, traits> y) noexcept;
+    template<class charT, class traits>                                                            // Since C++20
+    constexpr see below operator<=>(basic_string_view<charT, traits> x,
+                                    basic_string_view<charT, traits> y) noexcept;
+
     // see below, sufficient additional overloads of comparison functions
 
     // 7.10, Inserters and extractors
@@ -770,6 +776,8 @@
     return __lhs.compare(__rhs) == 0;
 }
 
+#if _LIBCPP_STD_VER < 20
+// This overload is automatically generated in C++20.
 template<class _CharT, class _Traits, int = 2>
 _LIBCPP_CONSTEXPR_AFTER_CXX11 _LIBCPP_INLINE_VISIBILITY
 bool operator==(typename common_type<basic_string_view<_CharT, _Traits> >::type __lhs,
@@ -778,7 +786,41 @@
     if ( __lhs.size() != __rhs.size()) return false;
     return __lhs.compare(__rhs) == 0;
 }
+#endif // _LIBCPP_STD_VER > 17
 
+// operator <=>
+
+#if _LIBCPP_STD_VER > 17
+
+template <class _CharT, class _Traits>
+_LIBCPP_HIDE_FROM_ABI constexpr auto
+operator<=>(basic_string_view<_CharT, _Traits> __lhs, basic_string_view<_CharT, _Traits> __rhs) noexcept {
+    if constexpr (requires { typename _Traits::comparison_category; }) {
+        // [string.view]/4
+        static_assert(
+            __comparison_category<typename _Traits::comparison_category>,
+            "return type is not a comparison category type");
+        return static_cast<typename _Traits::comparison_category>(__lhs.compare(__rhs) <=> 0);
+    } else {
+        return static_cast<weak_ordering>(__lhs.compare(__rhs) <=> 0);
+    }
+}
+
+template <class _CharT, class _Traits, int = 1>
+_LIBCPP_HIDE_FROM_ABI constexpr auto operator<=>(
+    basic_string_view<_CharT, _Traits> __lhs, common_type_t<basic_string_view<_CharT, _Traits>> __rhs) noexcept {
+    if constexpr (requires { typename _Traits::comparison_category; }) {
+        // [string.view]/4
+        static_assert(
+            __comparison_category<typename _Traits::comparison_category>,
+            "return type is not a comparison category type");
+        return static_cast<typename _Traits::comparison_category>(__lhs.compare(__rhs) <=> 0);
+    } else {
+        return static_cast<weak_ordering>(__lhs.compare(__rhs) <=> 0);
+    }
+}
+
+#else //  _LIBCPP_STD_VER > 17
 
 // operator !=
 template<class _CharT, class _Traits>
@@ -911,6 +953,7 @@
     return __lhs.compare(__rhs) >= 0;
 }
 
+#endif //  _LIBCPP_STD_VER > 17
 
 template<class _CharT, class _Traits>
 basic_ostream<_CharT, _Traits>&
diff --git a/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char/types.pass.cpp b/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char/types.pass.cpp
index ffbb735..f3b2505 100644
--- a/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char/types.pass.cpp
+++ b/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char/types.pass.cpp
@@ -15,6 +15,7 @@
 // typedef streamoff off_type;
 // typedef streampos pos_type;
 // typedef mbstate_t state_type;
+// using comparison_category = strong_ordering;
 
 #include <string>
 #include <type_traits>
@@ -28,6 +29,9 @@
     static_assert((std::is_same<std::char_traits<char>::off_type, std::streamoff>::value), "");
     static_assert((std::is_same<std::char_traits<char>::pos_type, std::streampos>::value), "");
     static_assert((std::is_same<std::char_traits<char>::state_type, std::mbstate_t>::value), "");
+#if TEST_STD_VER > 17
+    static_assert(std::is_same_v<std::char_traits<char>::comparison_category, std::strong_ordering>);
+#endif
 
   return 0;
 }
diff --git a/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char16_t/types.pass.cpp b/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char16_t/types.pass.cpp
index 6624f50..3dbcf27 100644
--- a/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char16_t/types.pass.cpp
+++ b/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char16_t/types.pass.cpp
@@ -15,6 +15,7 @@
 // typedef streamoff      off_type;
 // typedef u16streampos   pos_type;
 // typedef mbstate_t      state_type;
+// using comparison_category = strong_ordering;
 
 #include <string>
 #include <type_traits>
@@ -29,6 +30,9 @@
     static_assert((std::is_same<std::char_traits<char16_t>::off_type, std::streamoff>::value), "");
     static_assert((std::is_same<std::char_traits<char16_t>::pos_type, std::u16streampos>::value), "");
     static_assert((std::is_same<std::char_traits<char16_t>::state_type, std::mbstate_t>::value), "");
+#if TEST_STD_VER > 17
+    static_assert(std::is_same_v<std::char_traits<char16_t>::comparison_category, std::strong_ordering>);
+#endif
 
     return 0;
 }
diff --git a/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char32_t/types.compile.pass.cpp b/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char32_t/types.compile.pass.cpp
index 7ef1eac..aa44829 100644
--- a/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char32_t/types.compile.pass.cpp
+++ b/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char32_t/types.compile.pass.cpp
@@ -15,13 +15,19 @@
 // typedef streamoff      off_type;
 // typedef u32streampos   pos_type;
 // typedef mbstate_t      state_type;
+// using comparison_category = strong_ordering;
 
 #include <string>
 #include <type_traits>
 #include <cstdint>
 
+#include "test_macros.h"
+
 static_assert((std::is_same<std::char_traits<char32_t>::char_type, char32_t>::value), "");
 static_assert((std::is_same<std::char_traits<char32_t>::int_type, std::uint_least32_t>::value), "");
 static_assert((std::is_same<std::char_traits<char32_t>::off_type, std::streamoff>::value), "");
 static_assert((std::is_same<std::char_traits<char32_t>::pos_type, std::u32streampos>::value), "");
 static_assert((std::is_same<std::char_traits<char32_t>::state_type, std::mbstate_t>::value), "");
+#if TEST_STD_VER > 17
+static_assert(std::is_same_v<std::char_traits<char32_t>::comparison_category, std::strong_ordering>);
+#endif
diff --git a/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char8_t/types.pass.cpp b/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char8_t/types.pass.cpp
index 53e1020..b04cf71 100644
--- a/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char8_t/types.pass.cpp
+++ b/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.char8_t/types.pass.cpp
@@ -16,6 +16,7 @@
 // typedef streamoff      off_type;
 // typedef u8streampos    pos_type;
 // typedef mbstate_t      state_type;
+// using comparison_category = strong_ordering;
 
 #include <string>
 #include <type_traits>
@@ -31,6 +32,7 @@
     static_assert((std::is_same<std::char_traits<char8_t>::off_type,   std::streamoff>::value), "");
     static_assert((std::is_same<std::char_traits<char8_t>::pos_type,   std::u8streampos>::value), "");
     static_assert((std::is_same<std::char_traits<char8_t>::state_type, std::mbstate_t>::value), "");
+    static_assert(std::is_same_v<std::char_traits<char8_t>::comparison_category, std::strong_ordering>);
 #endif
 
   return 0;
diff --git a/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.wchar.t/types.pass.cpp b/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.wchar.t/types.pass.cpp
index 5e0e3a1..1c93274 100644
--- a/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.wchar.t/types.pass.cpp
+++ b/test/std/strings/char.traits/char.traits.specializations/char.traits.specializations.wchar.t/types.pass.cpp
@@ -15,6 +15,7 @@
 // typedef streamoff off_type;
 // typedef streampos pos_type;
 // typedef mbstate_t state_type;
+// using comparison_category = strong_ordering;
 
 // UNSUPPORTED: no-wide-characters
 
@@ -30,6 +31,9 @@
     static_assert((std::is_same<std::char_traits<wchar_t>::off_type, std::streamoff>::value), "");
     static_assert((std::is_same<std::char_traits<wchar_t>::pos_type, std::wstreampos>::value), "");
     static_assert((std::is_same<std::char_traits<wchar_t>::state_type, std::mbstate_t>::value), "");
+#if TEST_STD_VER > 17
+    static_assert(std::is_same_v<std::char_traits<wchar_t>::comparison_category, std::strong_ordering>);
+#endif
 
   return 0;
 }
diff --git a/test/std/strings/string.view/string.view.comparison/comparison.pass.cpp b/test/std/strings/string.view/string.view.comparison/comparison.pass.cpp
new file mode 100644
index 0000000..120b9a0
--- /dev/null
+++ b/test/std/strings/string.view/string.view.comparison/comparison.pass.cpp
@@ -0,0 +1,149 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// Starting with C++20 the spaceship operator was included. This tests
+// comparison in that context, thus doesn't support older language versions.
+// These are tested per operator.
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// <string_view>
+
+// template<class charT, class traits>
+//   constexpr bool operator==(basic_string_view<charT, traits> lhs, basic_string_view<charT, traits> rhs);
+// template<class charT, class traits>
+//   constexpr auto operator<=>(basic_string_view<charT, traits> lhs, basic_string_view<charT, traits> rhs);
+// (plus "sufficient additional overloads" to make implicit conversions work as intended)
+
+#include <string_view>
+
+#include <array>
+#include <cassert>
+#include <string>
+
+#include "constexpr_char_traits.h"
+#include "make_string.h"
+#include "test_comparisons.h"
+#include "test_macros.h"
+
+#define SV(S) MAKE_STRING_VIEW(CharT, S)
+
+// Copied from constexpr_char_traits, but it doesn't have a full implementation.
+// It has a comparison_category used in the tests.
+template <class CharT, class Ordering>
+struct char_traits {
+  using char_type           = CharT;
+  using int_type            = int;
+  using off_type            = std::streamoff;
+  using pos_type            = std::streampos;
+  using state_type          = std::mbstate_t;
+  using comparison_category = Ordering;
+
+  static constexpr void assign(char_type& __c1, const char_type& __c2) noexcept { __c1 = __c2; }
+  static constexpr bool eq(char_type __c1, char_type __c2) noexcept { return __c1 == __c2; }
+  static constexpr bool lt(char_type __c1, char_type __c2) noexcept { return __c1 < __c2; }
+  static constexpr int compare(const char_type* __s1, const char_type* __s2, size_t __n) {
+    for (; __n; --__n, ++__s1, ++__s2) {
+      if (lt(*__s1, *__s2))
+        return -1;
+      if (lt(*__s2, *__s1))
+        return 1;
+    }
+    return 0;
+  }
+
+  static constexpr size_t length(const char_type* __s);
+  static constexpr const char_type* find(const char_type* __s, size_t __n, const char_type& __a);
+  static constexpr char_type* move(char_type* __s1, const char_type* __s2, size_t __n);
+  static constexpr char_type* copy(char_type* __s1, const char_type* __s2, size_t __n);
+  static constexpr char_type* assign(char_type* __s, size_t __n, char_type __a);
+  static constexpr int_type not_eof(int_type __c) noexcept { return eq_int_type(__c, eof()) ? ~eof() : __c; }
+  static constexpr char_type to_char_type(int_type __c) noexcept { return char_type(__c); }
+  static constexpr int_type to_int_type(char_type __c) noexcept { return int_type(__c); }
+  static constexpr bool eq_int_type(int_type __c1, int_type __c2) noexcept { return __c1 == __c2; }
+  static constexpr int_type eof() noexcept { return int_type(EOF); }
+};
+
+template <class T, class Ordering = std::strong_ordering>
+constexpr void test() {
+  AssertOrderAreNoexcept<T>();
+  AssertOrderReturn<Ordering, T>();
+
+  using CharT = typename T::value_type;
+
+  // sorted values
+  std::array v{
+      SV(""),
+      SV("abc"),
+      SV("abcdef"),
+  };
+
+  // sorted values with embedded NUL character
+  std::array vn{
+      SV("abc"),
+      SV("abc\0"),
+      SV("abc\0def"),
+  };
+  static_assert(v.size() == vn.size());
+
+  for (size_t i = 0; i < v.size(); ++i) {
+    for (size_t j = 0; j < v.size(); ++j) {
+      assert(testOrder(v[i], v[j], i == j ? Ordering::equivalent : i < j ? Ordering::less : Ordering::greater));
+      assert(testOrder(
+          v[i],
+          std::basic_string<CharT>{v[j]},
+          i == j  ? Ordering::equivalent
+          : i < j ? Ordering::less
+                  : Ordering::greater));
+
+      assert(testOrder(
+          v[i],
+          std::basic_string<CharT>{v[j]}.c_str(),
+          i == j  ? Ordering::equivalent
+          : i < j ? Ordering::less
+                  : Ordering::greater));
+
+      // NUL test omitted for c-strings since it will fail.
+      assert(testOrder(vn[i], vn[j], i == j ? Ordering::equivalent : i < j ? Ordering::less : Ordering::greater));
+      assert(testOrder(
+          vn[i],
+          std::basic_string<CharT>{vn[j]},
+          i == j  ? Ordering::equivalent
+          : i < j ? Ordering::less
+                  : Ordering::greater));
+    }
+  }
+}
+
+template <class CharT>
+constexpr void test_all_orderings() {
+  test<std::basic_string_view<CharT>>(); // Strong ordering in its char_traits
+  test<std::basic_string_view<CharT, constexpr_char_traits<CharT>>,
+       std::weak_ordering>(); // No ordering in its char_traits
+  test<std::basic_string_view<CharT, char_traits<CharT, std::weak_ordering>>, std::weak_ordering>();
+  test<std::basic_string_view<CharT, char_traits<CharT, std::partial_ordering>>, std::partial_ordering>();
+}
+
+constexpr bool test_all_types() {
+  test_all_orderings<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_all_orderings<wchar_t>();
+#endif
+  test_all_orderings<char8_t>();
+  test_all_orderings<char16_t>();
+  test_all_orderings<char32_t>();
+
+  return true;
+}
+
+int main(int, char**) {
+  test_all_types();
+  static_assert(test_all_types());
+
+  return 0;
+}
diff --git a/test/std/strings/string.view/string.view.comparison/comparison.verify.cpp b/test/std/strings/string.view/string.view.comparison/comparison.verify.cpp
new file mode 100644
index 0000000..09bd1bd
--- /dev/null
+++ b/test/std/strings/string.view/string.view.comparison/comparison.verify.cpp
@@ -0,0 +1,96 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <string_view>
+
+// template<class charT, class traits>
+//   constexpr auto operator<=>(basic_string_view<charT, traits> lhs, basic_string_view<charT, traits> rhs);
+//
+// LWG 3432
+// [string.view]/4
+// Mandates: R denotes a comparison category type ([cmp.categories]).
+
+#include <string_view>
+
+#include "test_macros.h"
+
+template <class CharT, class Ordering>
+struct traits {
+  typedef CharT char_type;
+  typedef int int_type;
+  typedef std::streamoff off_type;
+  typedef std::streampos pos_type;
+  typedef std::mbstate_t state_type;
+  using comparison_category = Ordering;
+
+  static constexpr void assign(char_type&, const char_type&) noexcept;
+  static constexpr bool eq(char_type&, const char_type&) noexcept;
+  static constexpr bool lt(char_type&, const char_type&) noexcept;
+
+  static constexpr int compare(const char_type*, const char_type*, size_t) { return 0; }
+  static constexpr size_t length(const char_type*);
+  static constexpr const char_type* find(const char_type*, size_t, const char_type&);
+  static constexpr char_type* move(char_type*, const char_type*, size_t);
+  static constexpr char_type* copy(char_type*, const char_type*, size_t);
+  static constexpr char_type* assign(char_type*, size_t, char_type);
+
+  static constexpr int_type not_eof(int_type) noexcept;
+
+  static constexpr char_type to_char_type(int_type) noexcept;
+
+  static constexpr int_type to_int_type(char_type) noexcept;
+
+  static constexpr bool eq_int_type(int_type, int_type) noexcept;
+
+  static constexpr int_type eof() noexcept;
+};
+
+template <class CharT, class Ordering, bool Valid>
+void test() {
+  using type = std::basic_string_view<CharT, traits<CharT, Ordering>>;
+  if constexpr (Valid)
+    type{} <=> type{};
+  else
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  // These diagnostics are issued for
+  // - Every invalid ordering
+  // - Every type
+  // expected-error-re@string_view:* 15 {{{{(static_assert|static assertion)}} failed{{.*}}return type is not a comparison category type}}
+
+  // This diagnostic is not issued for Ordering == void.
+  // expected-error@string_view:* 10 {{no matching conversion for static_cast from}}
+#else
+  // One less test run when wchar_t is unavailable.
+  // expected-error-re@string_view:* 12 {{{{(static_assert|static assertion)}} failed{{.*}}return type is not a comparison category type}}
+  // expected-error@string_view:* 8 {{no matching conversion for static_cast from}}
+#endif
+    type{} <=> type{};
+}
+
+template <class CharT>
+void test_all_orders() {
+  test<CharT, std::strong_ordering, true>();
+  test<CharT, std::weak_ordering, true>();
+  test<CharT, std::partial_ordering, true>();
+
+  test<CharT, void, false>();
+  test<CharT, int, false>();
+  test<CharT, float, false>();
+}
+
+void test_all_types() {
+  test_all_orders<char>();
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+  test_all_orders<wchar_t>();
+#endif
+  test_all_orders<char8_t>();
+  test_all_orders<char16_t>();
+  test_all_orders<char32_t>();
+}
diff --git a/test/support/constexpr_char_traits.h b/test/support/constexpr_char_traits.h
index 5bf8fc8..7eb95d4 100644
--- a/test/support/constexpr_char_traits.h
+++ b/test/support/constexpr_char_traits.h
@@ -23,6 +23,8 @@
     typedef std::streamoff off_type;
     typedef std::streampos pos_type;
     typedef std::mbstate_t state_type;
+    // The comparison_category is omitted so the class will have weak_ordering
+    // in C++20. This is intentional.
 
     static TEST_CONSTEXPR_CXX14 void assign(char_type& __c1, const char_type& __c2) TEST_NOEXCEPT
         {__c1 = __c2;}
diff --git a/test/support/test_comparisons.h b/test/support/test_comparisons.h
index 9abded5..cc60577 100644
--- a/test/support/test_comparisons.h
+++ b/test/support/test_comparisons.h
@@ -25,6 +25,7 @@
 
 #include <type_traits>
 #include <cassert>
+#include <concepts>
 #include "test_macros.h"
 
 //  Test all six comparison operations for sanity
@@ -92,8 +93,7 @@
 }
 
 template <class T, class U = T>
-void AssertComparisonsAreNoexcept()
-{
+TEST_CONSTEXPR_CXX14 void AssertComparisonsAreNoexcept() {
     ASSERT_NOEXCEPT(std::declval<const T&>() == std::declval<const U&>());
     ASSERT_NOEXCEPT(std::declval<const T&>() != std::declval<const U&>());
     ASSERT_NOEXCEPT(std::declval<const T&>() <  std::declval<const U&>());
@@ -103,8 +103,7 @@
 }
 
 template <class T, class U = T>
-void AssertComparisonsReturnBool()
-{
+TEST_CONSTEXPR_CXX14 void AssertComparisonsReturnBool() {
     ASSERT_SAME_TYPE(decltype(std::declval<const T&>() == std::declval<const U&>()), bool);
     ASSERT_SAME_TYPE(decltype(std::declval<const T&>() != std::declval<const U&>()), bool);
     ASSERT_SAME_TYPE(decltype(std::declval<const T&>() <  std::declval<const U&>()), bool);
@@ -113,7 +112,6 @@
     ASSERT_SAME_TYPE(decltype(std::declval<const T&>() >= std::declval<const U&>()), bool);
 }
 
-
 template <class T, class U = T>
 void AssertComparisonsConvertibleToBool()
 {
@@ -127,21 +125,26 @@
 
 #if TEST_STD_VER > 17
 template <class T, class U = T>
-void AssertOrderAreNoexcept() {
-  AssertComparisonsAreNoexcept<T, U>();
-  ASSERT_NOEXCEPT(std::declval<const T&>() <=> std::declval<const U&>());
+constexpr void AssertOrderAreNoexcept() {
+    AssertComparisonsAreNoexcept<T, U>();
+    ASSERT_NOEXCEPT(std::declval<const T&>() <=> std::declval<const U&>());
 }
 
 template <class Order, class T, class U = T>
-void AssertOrderReturn() {
-  AssertComparisonsReturnBool<T, U>();
-  ASSERT_SAME_TYPE(decltype(std::declval<const T&>() <=> std::declval<const U&>()), Order);
+constexpr void AssertOrderReturn() {
+    AssertComparisonsReturnBool<T, U>();
+    ASSERT_SAME_TYPE(decltype(std::declval<const T&>() <=> std::declval<const U&>()), Order);
 }
 
 template <class Order, class T, class U = T>
 constexpr bool testOrder(const T& t1, const U& t2, Order order) {
-  return (t1 <=> t2 == order) &&
-         testComparisons(t1, t2, order == Order::equal || order == Order::equivalent, order == Order::less);
+    bool equal = order == Order::equivalent;
+    if constexpr (std::same_as<Order, std::strong_ordering>)
+        equal |= order == Order::equal;
+
+    bool less = order == Order::less;
+
+    return (t1 <=> t2 == order) && testComparisons(t1, t2, equal, less);
 }
 
 template <class T, class Param>