[libc++] [C++20] [P0586] Implement safe integral comparisons

* https://wg21.link/P0586

Reviewed By: #libc, curdeius, Quuxplusone

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

GitOrigin-RevId: 36c3918ec55b49180933acceb36dbd9e8b5c97ed
diff --git a/docs/Cxx2aStatusPaperStatus.csv b/docs/Cxx2aStatusPaperStatus.csv
index 1c5a84c..ce4da44 100644
--- a/docs/Cxx2aStatusPaperStatus.csv
+++ b/docs/Cxx2aStatusPaperStatus.csv
@@ -164,7 +164,7 @@
 "`P1961 <https://wg21.link/P1961>`__","LWG","Harmonizing the definitions of total order for pointers","Belfast","* *",""
 "`P1965 <https://wg21.link/P1965>`__","LWG","Blanket Wording for Specifying ""Hidden Friends""","Belfast","* *",""
 "","","","","",""
-"`P0586 <https://wg21.link/P0586>`__","LWG","Safe integral comparisons","Prague","* *",""
+"`P0586 <https://wg21.link/P0586>`__","LWG","Safe integral comparisons","Prague","|Complete|","13.0"
 "`P0593 <https://wg21.link/P0593>`__","CWG","Implicit creation of objects for low-level object manipulation","Prague","* *",""
 "`P1115 <https://wg21.link/P1115>`__","LWG","Improving the Return Value of Erase-Like Algorithms II: Free erase/erase if","Prague","|Complete|","11.0"
 "`P1243 <https://wg21.link/P1243>`__","LWG","Rangify New Algorithms","Prague","* *",""
diff --git a/docs/FeatureTestMacroTable.rst b/docs/FeatureTestMacroTable.rst
index 8c4f2a8..ff5197f 100644
--- a/docs/FeatureTestMacroTable.rst
+++ b/docs/FeatureTestMacroTable.rst
@@ -240,7 +240,7 @@
     ------------------------------------------------- -----------------
     ``__cpp_lib_int_pow2``                            ``202002L``
     ------------------------------------------------- -----------------
-    ``__cpp_lib_integer_comparison_functions``        *unimplemented*
+    ``__cpp_lib_integer_comparison_functions``        ``202002L``
     ------------------------------------------------- -----------------
     ``__cpp_lib_interpolate``                         ``201902L``
     ------------------------------------------------- -----------------
diff --git a/include/utility b/include/utility
index 925b952..a49826e 100644
--- a/include/utility
+++ b/include/utility
@@ -58,6 +58,14 @@
 
 template <class T> typename add_rvalue_reference<T>::type declval() noexcept;
 
+template<class T, class U> constexpr bool cmp_equal(T t, U u) noexcept;         // C++20
+template<class T, class U> constexpr bool cmp_not_equal(T t, U u) noexcept;     // C++20
+template<class T, class U> constexpr bool cmp_less(T t, U u) noexcept;          // C++20
+template<class T, class U> constexpr bool cmp_greater(T t, U u) noexcept;       // C++20
+template<class T, class U> constexpr bool cmp_less_equal(T t, U u) noexcept;    // C++20
+template<class T, class U> constexpr bool cmp_greater_equal(T t, U u) noexcept; // C++20
+template<class R, class T> constexpr bool in_range(T t) noexcept;               // C++20
+
 template <class T1, class T2>
 struct pair
 {
@@ -207,6 +215,7 @@
 #include <cstddef>
 #include <cstring>
 #include <cstdint>
+#include <limits>
 #include <version>
 #include <__debug>
 
@@ -214,6 +223,9 @@
 #pragma GCC system_header
 #endif
 
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
 _LIBCPP_BEGIN_NAMESPACE_STD
 
 namespace rel_ops
@@ -284,6 +296,82 @@
 void as_const(const _Tp&&) = delete;
 #endif
 
+#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_CONCEPTS)
+template<class _Tp, class... _Up>
+struct _IsSameAsAny : _Or<_IsSame<_Tp, _Up>...> {};
+
+template<class _Tp>
+concept __is_safe_integral_cmp = is_integral_v<_Tp> &&
+                      !_IsSameAsAny<_Tp, bool, char,
+#ifndef _LIBCPP_NO_HAS_CHAR8_T
+                                    char8_t,
+#endif
+#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS
+                                    char16_t, char32_t,
+#endif
+                                    wchar_t>::value;
+
+template<__is_safe_integral_cmp _Tp, __is_safe_integral_cmp _Up>
+_LIBCPP_INLINE_VISIBILITY constexpr
+bool cmp_equal(_Tp __t, _Up __u) noexcept
+{
+  if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>)
+    return __t == __u;
+  else if constexpr (is_signed_v<_Tp>)
+    return __t < 0 ? false : make_unsigned_t<_Tp>(__t) == __u;
+  else
+    return __u < 0 ? false : __t == make_unsigned_t<_Up>(__u);
+}
+
+template<__is_safe_integral_cmp _Tp, __is_safe_integral_cmp _Up>
+_LIBCPP_INLINE_VISIBILITY constexpr
+bool cmp_not_equal(_Tp __t, _Up __u) noexcept
+{
+  return !_VSTD::cmp_equal(__t, __u);
+}
+
+template<__is_safe_integral_cmp _Tp, __is_safe_integral_cmp _Up>
+_LIBCPP_INLINE_VISIBILITY constexpr
+bool cmp_less(_Tp __t, _Up __u) noexcept
+{
+  if constexpr (is_signed_v<_Tp> == is_signed_v<_Up>)
+    return __t < __u;
+  else if constexpr (is_signed_v<_Tp>)
+    return __t < 0 ? true : make_unsigned_t<_Tp>(__t) < __u;
+  else
+    return __u < 0 ? false : __t < make_unsigned_t<_Up>(__u);
+}
+
+template<__is_safe_integral_cmp _Tp, __is_safe_integral_cmp _Up>
+_LIBCPP_INLINE_VISIBILITY constexpr
+bool cmp_greater(_Tp __t, _Up __u) noexcept
+{
+  return _VSTD::cmp_less(__u, __t);
+}
+
+template<__is_safe_integral_cmp _Tp, __is_safe_integral_cmp _Up>
+_LIBCPP_INLINE_VISIBILITY constexpr
+bool cmp_less_equal(_Tp __t, _Up __u) noexcept
+{
+  return !_VSTD::cmp_greater(__t, __u);
+}
+
+template<__is_safe_integral_cmp _Tp, __is_safe_integral_cmp _Up>
+_LIBCPP_INLINE_VISIBILITY constexpr
+bool cmp_greater_equal(_Tp __t, _Up __u) noexcept
+{
+  return !_VSTD::cmp_less(__t, __u);
+}
+
+template<__is_safe_integral_cmp _Tp, __is_safe_integral_cmp _Up>
+_LIBCPP_INLINE_VISIBILITY constexpr
+bool in_range(_Up __u) noexcept
+{
+  return _VSTD::cmp_less_equal(__u, numeric_limits<_Tp>::max()) &&
+         _VSTD::cmp_greater_equal(__u, numeric_limits<_Tp>::min());
+}
+#endif
+
 struct _LIBCPP_TEMPLATE_VIS piecewise_construct_t { explicit piecewise_construct_t() = default; };
 #if defined(_LIBCPP_CXX03_LANG) || defined(_LIBCPP_BUILDING_LIBRARY)
 extern _LIBCPP_EXPORTED_FROM_ABI const piecewise_construct_t piecewise_construct;// = piecewise_construct_t();
@@ -1647,4 +1735,6 @@
 
 _LIBCPP_END_NAMESPACE_STD
 
+_LIBCPP_POP_MACROS
+
 #endif  // _LIBCPP_UTILITY
diff --git a/include/version b/include/version
index 9de078a..440fc59 100644
--- a/include/version
+++ b/include/version
@@ -322,7 +322,9 @@
 // # define __cpp_lib_format                               201907L
 # define __cpp_lib_generic_unordered_lookup             201811L
 # define __cpp_lib_int_pow2                             202002L
-// # define __cpp_lib_integer_comparison_functions         202002L
+# if !defined(_LIBCPP_HAS_NO_CONCEPTS)
+#   define __cpp_lib_integer_comparison_functions       202002L
+# endif
 # define __cpp_lib_interpolate                          201902L
 # if !defined(_LIBCPP_HAS_NO_BUILTIN_IS_CONSTANT_EVALUATED)
 #   define __cpp_lib_is_constant_evaluated              201811L
diff --git a/test/std/language.support/support.limits/support.limits.general/utility.version.pass.cpp b/test/std/language.support/support.limits/support.limits.general/utility.version.pass.cpp
index 1ec2f43..b5662f7 100644
--- a/test/std/language.support/support.limits/support.limits.general/utility.version.pass.cpp
+++ b/test/std/language.support/support.limits/support.limits.general/utility.version.pass.cpp
@@ -184,16 +184,16 @@
 #   error "__cpp_lib_exchange_function should have the value 201304L in c++20"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
+# if defined(__cpp_concepts) && __cpp_concepts >= 201907L
 #   ifndef __cpp_lib_integer_comparison_functions
 #     error "__cpp_lib_integer_comparison_functions should be defined in c++20"
 #   endif
 #   if __cpp_lib_integer_comparison_functions != 202002L
 #     error "__cpp_lib_integer_comparison_functions should have the value 202002L in c++20"
 #   endif
-# else // _LIBCPP_VERSION
+# else
 #   ifdef __cpp_lib_integer_comparison_functions
-#     error "__cpp_lib_integer_comparison_functions should not be defined because it is unimplemented in libc++!"
+#     error "__cpp_lib_integer_comparison_functions should not be defined when defined(__cpp_concepts) && __cpp_concepts >= 201907L is not defined!"
 #   endif
 # endif
 
@@ -251,16 +251,16 @@
 #   error "__cpp_lib_exchange_function should have the value 201304L in c++2b"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
+# if defined(__cpp_concepts) && __cpp_concepts >= 201907L
 #   ifndef __cpp_lib_integer_comparison_functions
 #     error "__cpp_lib_integer_comparison_functions should be defined in c++2b"
 #   endif
 #   if __cpp_lib_integer_comparison_functions != 202002L
 #     error "__cpp_lib_integer_comparison_functions should have the value 202002L in c++2b"
 #   endif
-# else // _LIBCPP_VERSION
+# else
 #   ifdef __cpp_lib_integer_comparison_functions
-#     error "__cpp_lib_integer_comparison_functions should not be defined because it is unimplemented in libc++!"
+#     error "__cpp_lib_integer_comparison_functions should not be defined when defined(__cpp_concepts) && __cpp_concepts >= 201907L is not defined!"
 #   endif
 # endif
 
diff --git a/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp b/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp
index f83917f..64db73f 100644
--- a/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp
+++ b/test/std/language.support/support.limits/support.limits.general/version.version.pass.cpp
@@ -2653,16 +2653,16 @@
 #   error "__cpp_lib_int_pow2 should have the value 202002L in c++20"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
+# if defined(__cpp_concepts) && __cpp_concepts >= 201907L
 #   ifndef __cpp_lib_integer_comparison_functions
 #     error "__cpp_lib_integer_comparison_functions should be defined in c++20"
 #   endif
 #   if __cpp_lib_integer_comparison_functions != 202002L
 #     error "__cpp_lib_integer_comparison_functions should have the value 202002L in c++20"
 #   endif
-# else // _LIBCPP_VERSION
+# else
 #   ifdef __cpp_lib_integer_comparison_functions
-#     error "__cpp_lib_integer_comparison_functions should not be defined because it is unimplemented in libc++!"
+#     error "__cpp_lib_integer_comparison_functions should not be defined when defined(__cpp_concepts) && __cpp_concepts >= 201907L is not defined!"
 #   endif
 # endif
 
@@ -3834,16 +3834,16 @@
 #   error "__cpp_lib_int_pow2 should have the value 202002L in c++2b"
 # endif
 
-# if !defined(_LIBCPP_VERSION)
+# if defined(__cpp_concepts) && __cpp_concepts >= 201907L
 #   ifndef __cpp_lib_integer_comparison_functions
 #     error "__cpp_lib_integer_comparison_functions should be defined in c++2b"
 #   endif
 #   if __cpp_lib_integer_comparison_functions != 202002L
 #     error "__cpp_lib_integer_comparison_functions should have the value 202002L in c++2b"
 #   endif
-# else // _LIBCPP_VERSION
+# else
 #   ifdef __cpp_lib_integer_comparison_functions
-#     error "__cpp_lib_integer_comparison_functions should not be defined because it is unimplemented in libc++!"
+#     error "__cpp_lib_integer_comparison_functions should not be defined when defined(__cpp_concepts) && __cpp_concepts >= 201907L is not defined!"
 #   endif
 # endif
 
diff --git a/test/std/utilities/utility/utility.intcmp/intcmp.cmp_equal/cmp_equal.pass.cpp b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_equal/cmp_equal.pass.cpp
new file mode 100644
index 0000000..7e88e4b
--- /dev/null
+++ b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_equal/cmp_equal.pass.cpp
@@ -0,0 +1,107 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <utility>
+
+// template<class T, class U>
+//   constexpr bool cmp_equal(T t, U u) noexcept;         // C++20
+
+#include <utility>
+#include <limits>
+#include <numeric>
+#include <tuple>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <typename T>
+struct Tuple {
+  T min;
+  T max;
+  T mid;
+  constexpr Tuple() {
+    min = std::numeric_limits<T>::min();
+    max = std::numeric_limits<T>::max();
+    mid = std::midpoint(min, max);
+  }
+};
+
+template <typename T>
+constexpr void test_cmp_equal1() {
+  constexpr Tuple<T> tup;
+  assert(std::cmp_equal(T(0), T(0)));
+  assert(std::cmp_equal(T(10), T(10)));
+  assert(std::cmp_equal(tup.min, tup.min));
+  assert(std::cmp_equal(tup.max, tup.max));
+  assert(!std::cmp_equal(T(0), T(1)));
+  assert(!std::cmp_equal(T(1), T(0)));
+  assert(!std::cmp_equal(T(5), T(10)));
+  assert(!std::cmp_equal(T(10), T(5)));
+  assert(!std::cmp_equal(tup.min, tup.max));
+  assert(!std::cmp_equal(tup.max, tup.min));
+  assert(!std::cmp_equal(1, tup.max));
+  assert(!std::cmp_equal(tup.max, 1));
+  assert(!std::cmp_equal(1, tup.min));
+  assert(!std::cmp_equal(tup.min, 1));
+  assert(std::cmp_equal(T(-5), T(-5)));
+  assert(!std::cmp_equal(-2, tup.min));
+  assert(!std::cmp_equal(tup.min, -2));
+  assert(!std::cmp_equal(-2, tup.max));
+  assert(!std::cmp_equal(tup.max, -2));
+}
+
+template <typename T, typename U>
+constexpr void test_cmp_equal2() {
+  constexpr Tuple<T> ttup;
+  constexpr Tuple<U> utup;
+  assert(std::cmp_equal(T(0), U(0)));
+  assert(std::cmp_equal(T(10), U(10)));
+  assert(!std::cmp_equal(T(0), U(1)));
+  assert(!std::cmp_equal(T(1), U(0)));
+  assert(!std::cmp_equal(T(5), U(10)));
+  assert(!std::cmp_equal(T(10), U(5)));
+  assert(!std::cmp_equal(ttup.min, utup.max));
+  assert(!std::cmp_equal(utup.min, ttup.max));
+}
+
+template <class... Ts>
+constexpr void test1(const std::tuple<Ts...>&) {
+  (test_cmp_equal1<Ts>() , ...);
+}
+
+template <class T, class... Us>
+constexpr void test2_impl(const std::tuple<Us...>&) {
+  (test_cmp_equal2<T, Us>() , ...);
+}
+
+template <class... Ts, class UTuple>
+constexpr void test2(const std::tuple<Ts...>&, const UTuple& utuple) {
+  (test2_impl<Ts>(utuple) , ...);
+}
+
+constexpr bool test() {
+  std::tuple<
+#ifndef _LIBCPP_HAS_NO_INT128
+      __int128_t, __uint128_t,
+#endif
+      unsigned long long, long long, unsigned long, long, unsigned int, int,
+      unsigned short, short, unsigned char, signed char> types;
+  test1(types);
+  test2(types, types);
+  return true;
+}
+
+int main() {
+  ASSERT_NOEXCEPT(std::cmp_equal(0, 0));
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/test/std/utilities/utility/utility.intcmp/intcmp.cmp_greater/cmp_greater.pass.cpp b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_greater/cmp_greater.pass.cpp
new file mode 100644
index 0000000..0182c45
--- /dev/null
+++ b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_greater/cmp_greater.pass.cpp
@@ -0,0 +1,98 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <utility>
+
+//   constexpr bool cmp_greater(T t, U u) noexcept;       // C++20
+
+#include <utility>
+#include <limits>
+#include <numeric>
+#include <tuple>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <typename T>
+struct Tuple {
+  T min;
+  T max;
+  T mid;
+  constexpr Tuple() {
+    min = std::numeric_limits<T>::min();
+    max = std::numeric_limits<T>::max();
+    mid = std::midpoint(min, max);
+  }
+};
+
+template <typename T>
+constexpr void test_cmp_greater1() {
+  constexpr Tuple<T> tup;
+  assert(!std::cmp_greater(T(0), T(1)));
+  assert(!std::cmp_greater(T(1), T(2)));
+  assert(!std::cmp_greater(tup.min, tup.max));
+  assert(!std::cmp_greater(tup.min, tup.mid));
+  assert(!std::cmp_greater(tup.mid, tup.max));
+  assert(std::cmp_greater(T(1), T(0)));
+  assert(std::cmp_greater(T(10), T(5)));
+  assert(std::cmp_greater(tup.max, tup.min));
+  assert(std::cmp_greater(tup.mid, tup.min));
+  assert(!std::cmp_greater(tup.mid, tup.mid));
+  assert(!std::cmp_greater(tup.min, tup.min));
+  assert(!std::cmp_greater(tup.max, tup.max));
+  assert(std::cmp_greater(tup.max, 1));
+  assert(std::cmp_greater(1, tup.min));
+  assert(!std::cmp_greater(T(-1), T(-1)));
+  assert(std::cmp_greater(-2, tup.min) == std::is_signed_v<T>);
+  assert(!std::cmp_greater(tup.min, -2) == std::is_signed_v<T>);
+  assert(!std::cmp_greater(-2, tup.max));
+  assert(std::cmp_greater(tup.max, -2));
+}
+
+template <typename T, typename U>
+constexpr void test_cmp_greater2() {
+  assert(!std::cmp_greater(T(0), U(1)));
+  assert(std::cmp_greater(T(1), U(0)));
+}
+
+template <class... Ts>
+constexpr void test1(const std::tuple<Ts...>&) {
+  (test_cmp_greater1<Ts>() , ...);
+}
+
+template <class T, class... Us>
+constexpr void test2_impl(const std::tuple<Us...>&) {
+  (test_cmp_greater2<T, Us>() , ...);
+}
+
+template <class... Ts, class UTuple>
+constexpr void test2(const std::tuple<Ts...>&, const UTuple& utuple) {
+  (test2_impl<Ts>(utuple) , ...);
+}
+
+constexpr bool test() {
+  std::tuple<
+#ifndef _LIBCPP_HAS_NO_INT128
+      __int128_t, __uint128_t,
+#endif
+      unsigned long long, long long, unsigned long, long, unsigned int, int,
+      unsigned short, short, unsigned char, signed char> types;
+  test1(types);
+  test2(types, types);
+  return true;
+}
+
+int main() {
+  ASSERT_NOEXCEPT(std::cmp_greater(1, 0));
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/test/std/utilities/utility/utility.intcmp/intcmp.cmp_greater_equal/cmp_greater_equal.pass.cpp b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_greater_equal/cmp_greater_equal.pass.cpp
new file mode 100644
index 0000000..4b25615
--- /dev/null
+++ b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_greater_equal/cmp_greater_equal.pass.cpp
@@ -0,0 +1,100 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <utility>
+
+//   constexpr bool cmp_greater_equal(T t, U u) noexcept; // C++20
+
+#include <utility>
+#include <limits>
+#include <numeric>
+#include <tuple>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <typename T>
+struct Tuple {
+  T min;
+  T max;
+  T mid;
+  constexpr Tuple() {
+    min = std::numeric_limits<T>::min();
+    max = std::numeric_limits<T>::max();
+    mid = std::midpoint(min, max);
+  }
+};
+
+template <typename T>
+constexpr void test_cmp_greater_equal1() {
+  constexpr Tuple<T> tup;
+  assert(!std::cmp_greater_equal(T(0), T(1)));
+  assert(!std::cmp_greater_equal(T(1), T(2)));
+  assert(!std::cmp_greater_equal(tup.min, tup.max));
+  assert(!std::cmp_greater_equal(tup.min, tup.mid));
+  assert(!std::cmp_greater_equal(tup.mid, tup.max));
+  assert(std::cmp_greater_equal(T(1), T(0)));
+  assert(std::cmp_greater_equal(T(10), T(5)));
+  assert(std::cmp_greater_equal(tup.max, tup.min));
+  assert(std::cmp_greater_equal(tup.mid, tup.min));
+  assert(std::cmp_greater_equal(tup.mid, tup.mid));
+  assert(std::cmp_greater_equal(tup.min, tup.min));
+  assert(std::cmp_greater_equal(tup.max, tup.max));
+  assert(std::cmp_greater_equal(tup.max, 1));
+  assert(std::cmp_greater_equal(1, tup.min));
+  assert(std::cmp_greater_equal(T(-1), T(-1)));
+  assert(std::cmp_greater_equal(-2, tup.min) == std::is_signed_v<T>);
+  assert(std::cmp_greater_equal(tup.min, -2) == std::is_unsigned_v<T>);
+  assert(!std::cmp_greater_equal(-2, tup.max));
+  assert(std::cmp_greater_equal(tup.max, -2));
+}
+
+template <typename T, typename U>
+constexpr void test_cmp_greater_equal2() {
+  assert(!std::cmp_greater_equal(T(0), U(1)));
+  assert(std::cmp_greater_equal(T(1), U(0)));
+  assert(std::cmp_greater_equal(T(0), U(0)));
+  assert(std::cmp_greater_equal(T(1), U(1)));
+}
+
+template <class... Ts>
+constexpr void test1(const std::tuple<Ts...>&) {
+  (test_cmp_greater_equal1<Ts>() , ...);
+}
+
+template <class T, class... Us>
+constexpr void test2_impl(const std::tuple<Us...>&) {
+  (test_cmp_greater_equal2<T, Us>() , ...);
+}
+
+template <class... Ts, class UTuple>
+constexpr void test2(const std::tuple<Ts...>&, const UTuple& utuple) {
+  (test2_impl<Ts>(utuple) , ...);
+}
+
+constexpr bool test() {
+  std::tuple<
+#ifndef _LIBCPP_HAS_NO_INT128
+      __int128_t, __uint128_t,
+#endif
+      unsigned long long, long long, unsigned long, long, unsigned int, int,
+      unsigned short, short, unsigned char, signed char> types;
+  test1(types);
+  test2(types, types);
+  return true;
+}
+
+int main() {
+  ASSERT_NOEXCEPT(std::cmp_greater_equal(1, 0));
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/test/std/utilities/utility/utility.intcmp/intcmp.cmp_less/cmp_less.pass.cpp b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_less/cmp_less.pass.cpp
new file mode 100644
index 0000000..70edebf
--- /dev/null
+++ b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_less/cmp_less.pass.cpp
@@ -0,0 +1,99 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <utility>
+
+// template<class T, class U>
+//   constexpr bool cmp_less(T t, U u) noexcept;          // C++20
+
+#include <utility>
+#include <limits>
+#include <numeric>
+#include <tuple>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <typename T>
+struct Tuple {
+  T min;
+  T max;
+  T mid;
+  constexpr Tuple() {
+    min = std::numeric_limits<T>::min();
+    max = std::numeric_limits<T>::max();
+    mid = std::midpoint(min, max);
+  }
+};
+
+template <typename T>
+constexpr void test_cmp_less1() {
+  constexpr Tuple<T> tup;
+  assert(std::cmp_less(T(0), T(1)));
+  assert(std::cmp_less(T(1), T(2)));
+  assert(std::cmp_less(tup.min, tup.max));
+  assert(std::cmp_less(tup.min, tup.mid));
+  assert(std::cmp_less(tup.mid, tup.max));
+  assert(!std::cmp_less(T(1), T(0)));
+  assert(!std::cmp_less(T(10), T(5)));
+  assert(!std::cmp_less(tup.max, tup.min));
+  assert(!std::cmp_less(tup.mid, tup.min));
+  assert(!std::cmp_less(tup.mid, tup.mid));
+  assert(!std::cmp_less(tup.min, tup.min));
+  assert(!std::cmp_less(tup.max, tup.max));
+  assert(!std::cmp_less(tup.max, 1));
+  assert(!std::cmp_less(1, tup.min));
+  assert(!std::cmp_less(T(-1), T(-1)));
+  assert(!std::cmp_less(-2, tup.min) == std::is_signed_v<T>);
+  assert(std::cmp_less(tup.min, -2) == std::is_signed_v<T>);
+  assert(std::cmp_less(-2, tup.max));
+  assert(!std::cmp_less(tup.max, -2));
+}
+
+template <typename T, typename U>
+constexpr void test_cmp_less2() {
+  assert(std::cmp_less(T(0), U(1)));
+  assert(!std::cmp_less(T(1), U(0)));
+}
+
+template <class... Ts>
+constexpr void test1(const std::tuple<Ts...>&) {
+  (test_cmp_less1<Ts>() , ...);
+}
+
+template <class T, class... Us>
+constexpr void test2_impl(const std::tuple<Us...>&) {
+  (test_cmp_less2<T, Us>() , ...);
+}
+
+template <class... Ts, class UTuple>
+constexpr void test2(const std::tuple<Ts...>&, const UTuple& utuple) {
+  (test2_impl<Ts>(utuple) , ...);
+}
+
+constexpr bool test() {
+  std::tuple<
+#ifndef _LIBCPP_HAS_NO_INT128
+      __int128_t, __uint128_t,
+#endif
+      unsigned long long, long long, unsigned long, long, unsigned int, int,
+      unsigned short, short, unsigned char, signed char> types;
+  test1(types);
+  test2(types, types);
+  return true;
+}
+
+int main() {
+  ASSERT_NOEXCEPT(std::cmp_less(0, 1));
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/test/std/utilities/utility/utility.intcmp/intcmp.cmp_less_equal/cmp_less_equal.pass.cpp b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_less_equal/cmp_less_equal.pass.cpp
new file mode 100644
index 0000000..78c3c2a
--- /dev/null
+++ b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_less_equal/cmp_less_equal.pass.cpp
@@ -0,0 +1,99 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <utility>
+
+//   constexpr bool cmp_less_equal(T t, U u) noexcept;    // C++20
+
+#include <utility>
+#include <limits>
+#include <numeric>
+#include <tuple>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <typename T>
+struct Tuple {
+  T min;
+  T max;
+  T mid;
+  constexpr Tuple() {
+    min = std::numeric_limits<T>::min();
+    max = std::numeric_limits<T>::max();
+    mid = std::midpoint(min, max);
+  }
+};
+
+template <typename T>
+constexpr void test_cmp_less_equal1() {
+  constexpr Tuple<T> tup;
+  assert(std::cmp_less_equal(T(0), T(0)));
+  assert(std::cmp_less_equal(T(0), T(1)));
+  assert(std::cmp_less_equal(tup.min, tup.max));
+  assert(std::cmp_less_equal(tup.min, tup.mid));
+  assert(std::cmp_less_equal(tup.mid, tup.max));
+  assert(std::cmp_less_equal(tup.max, tup.max));
+  assert(std::cmp_less_equal(tup.mid, tup.mid));
+  assert(std::cmp_less_equal(tup.min, tup.min));
+  assert(!std::cmp_less_equal(T(1), T(0)));
+  assert(!std::cmp_less_equal(T(10), T(5)));
+  assert(!std::cmp_less_equal(tup.max, tup.min));
+  assert(!std::cmp_less_equal(tup.mid, tup.min));
+  assert(!std::cmp_less_equal(tup.max, 1));
+  assert(!std::cmp_less_equal(1, tup.min));
+  assert(std::cmp_less_equal(T(-1), T(-1)));
+  assert(!std::cmp_less_equal(-2, tup.min) == std::is_signed_v<T>);
+  assert(std::cmp_less_equal(tup.min, -2) == std::is_signed_v<T>);
+  assert(std::cmp_less_equal(-2, tup.max));
+  assert(!std::cmp_less_equal(tup.max, -2));
+}
+
+template <typename T, typename U>
+constexpr void test_cmp_less_equal2() {
+  assert(std::cmp_less_equal(T(0), U(1)));
+  assert(std::cmp_less_equal(T(0), U(0)));
+  assert(!std::cmp_less_equal(T(1), U(0)));
+}
+
+template <class... Ts>
+constexpr void test1(const std::tuple<Ts...>&) {
+  (test_cmp_less_equal1<Ts>() , ...);
+}
+
+template <class T, class... Us>
+constexpr void test2_impl(const std::tuple<Us...>&) {
+  (test_cmp_less_equal2<T, Us>() , ...);
+}
+
+template <class... Ts, class UTuple>
+constexpr void test2(const std::tuple<Ts...>&, const UTuple& utuple) {
+  (test2_impl<Ts>(utuple) , ...);
+}
+
+constexpr bool test() {
+  std::tuple<
+#ifndef _LIBCPP_HAS_NO_INT128
+      __int128_t, __uint128_t,
+#endif
+      unsigned long long, long long, unsigned long, long, unsigned int, int,
+      unsigned short, short, unsigned char, signed char> types;
+  test1(types);
+  test2(types, types);
+  return true;
+}
+
+int main() {
+  ASSERT_NOEXCEPT(std::cmp_less_equal(0, 1));
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/test/std/utilities/utility/utility.intcmp/intcmp.cmp_not_equal/cmp_not_equal.pass.cpp b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_not_equal/cmp_not_equal.pass.cpp
new file mode 100644
index 0000000..70c2901
--- /dev/null
+++ b/test/std/utilities/utility/utility.intcmp/intcmp.cmp_not_equal/cmp_not_equal.pass.cpp
@@ -0,0 +1,106 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <utility>
+
+// template<class T, class U>
+//   constexpr bool cmp_not_equal(T t, U u) noexcept;     // C++20
+
+#include <utility>
+#include <limits>
+#include <numeric>
+#include <tuple>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <typename T>
+struct Tuple {
+  T min;
+  T max;
+  T mid;
+  constexpr Tuple() {
+    min = std::numeric_limits<T>::min();
+    max = std::numeric_limits<T>::max();
+    mid = std::midpoint(min, max);
+  }
+};
+
+template <typename T>
+constexpr void test_cmp_not_equal1() {
+  constexpr Tuple<T> tup;
+  assert(!std::cmp_not_equal(T(0), T(0)));
+  assert(!std::cmp_not_equal(T(10), T(10)));
+  assert(!std::cmp_not_equal(tup.min, tup.min));
+  assert(!std::cmp_not_equal(tup.max, tup.max));
+  assert(std::cmp_not_equal(T(0), T(1)));
+  assert(std::cmp_not_equal(T(1), T(0)));
+  assert(std::cmp_not_equal(T(5), T(10)));
+  assert(std::cmp_not_equal(T(10), T(5)));
+  assert(std::cmp_not_equal(tup.min, tup.max));
+  assert(std::cmp_not_equal(tup.max, tup.min));
+  assert(std::cmp_not_equal(1, tup.max));
+  assert(std::cmp_not_equal(tup.max, 1));
+  assert(std::cmp_not_equal(1, tup.min));
+  assert(std::cmp_not_equal(tup.min, 1));
+  assert(std::cmp_not_equal(-2, tup.min));
+  assert(std::cmp_not_equal(tup.min, -2));
+  assert(std::cmp_not_equal(-2, tup.max));
+  assert(std::cmp_not_equal(tup.max, -2));
+}
+
+template <typename T, typename U>
+constexpr void test_cmp_not_equal2() {
+  constexpr Tuple<T> ttup;
+  constexpr Tuple<U> utup;
+  assert(!std::cmp_not_equal(T(0), U(0)));
+  assert(!std::cmp_not_equal(T(10), U(10)));
+  assert(std::cmp_not_equal(T(0), U(1)));
+  assert(std::cmp_not_equal(T(1), U(0)));
+  assert(std::cmp_not_equal(T(5), U(10)));
+  assert(std::cmp_not_equal(T(10), U(5)));
+  assert(std::cmp_not_equal(ttup.min, utup.max));
+  assert(std::cmp_not_equal(utup.min, ttup.max));
+}
+
+template <class... Ts>
+constexpr void test1(const std::tuple<Ts...>&) {
+  (test_cmp_not_equal1<Ts>() , ...);
+}
+
+template <class T, class... Us>
+constexpr void test2_impl(const std::tuple<Us...>&) {
+  (test_cmp_not_equal2<T, Us>() , ...);
+}
+
+template <class... Ts, class UTuple>
+constexpr void test2(const std::tuple<Ts...>&, const UTuple& utuple) {
+  (test2_impl<Ts>(utuple) , ...);
+}
+
+constexpr bool test() {
+  std::tuple<
+#ifndef _LIBCPP_HAS_NO_INT128
+      __int128_t, __uint128_t,
+#endif
+      unsigned long long, long long, unsigned long, long, unsigned int, int,
+      unsigned short, short, unsigned char, signed char> types;
+  test1(types);
+  test2(types, types);
+  return true;
+}
+
+int main() {
+  ASSERT_NOEXCEPT(std::cmp_not_equal(0, 0));
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/test/std/utilities/utility/utility.intcmp/intcmp.fail.cpp b/test/std/utilities/utility/utility.intcmp/intcmp.fail.cpp
new file mode 100644
index 0000000..bd436dd
--- /dev/null
+++ b/test/std/utilities/utility/utility.intcmp/intcmp.fail.cpp
@@ -0,0 +1,147 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <utility>
+
+// template<class T, class U>
+//   constexpr bool cmp_equal(T t, U u) noexcept; // C++20
+
+// template<class T, class U>
+//   constexpr bool cmp_not_equal(T t, U u) noexcept; // C++20
+
+// template<class T, class U>
+//   constexpr bool cmp_less(T t, U u) noexcept; // C++20
+
+// template<class T, class U>
+//   constexpr bool cmp_less_equal(T t, U u) noexcept; // C++20
+
+// template<class T, class U>
+//   constexpr bool cmp_greater(T t, U u) noexcept; // C++20
+
+// template<class T, class U>
+//   constexpr bool cmp_greater_equal(T t, U u) noexcept; // C++20
+
+// template<class R, class T>
+//   constexpr bool in_range(T t) noexcept;      // C++20
+
+#include <utility>
+
+#include "test_macros.h"
+
+struct NonEmptyT {
+  int val;
+  NonEmptyT() : val(0) {}
+  NonEmptyT(int val) : val(val) {}
+  operator int&() { return val; }
+  operator int() const { return val; }
+};
+
+enum ColorT { red, green, blue };
+
+struct EmptyT {};
+
+template <class T>
+constexpr void test() {
+  std::cmp_equal(T(), T()); // expected-error11{{no matching function for call to 'cmp_equal'}}
+  std::cmp_equal(T(), int()); // expected-error11{{no matching function for call to 'cmp_equal'}}
+  std::cmp_equal(int(), T()); // expected-error11{{no matching function for call to 'cmp_equal'}}
+  std::cmp_not_equal(T(), T()); // expected-error11{{no matching function for call to 'cmp_not_equal'}}
+  std::cmp_not_equal(T(), int()); // expected-error11{{no matching function for call to 'cmp_not_equal'}}
+  std::cmp_not_equal(int(), T()); // expected-error11{{no matching function for call to 'cmp_not_equal'}}
+  std::cmp_less(T(), T()); // expected-error11{{no matching function for call to 'cmp_less'}}
+  std::cmp_less(T(), int()); // expected-error11{{no matching function for call to 'cmp_less'}}
+  std::cmp_less(int(), T()); // expected-error11{{no matching function for call to 'cmp_less'}}
+  std::cmp_less_equal(T(), T()); // expected-error11{{no matching function for call to 'cmp_less_equal'}}
+  std::cmp_less_equal(T(), int()); // expected-error11{{no matching function for call to 'cmp_less_equal'}}
+  std::cmp_less_equal(int(), T()); // expected-error11{{no matching function for call to 'cmp_less_equal'}}
+  std::cmp_greater(T(), T()); // expected-error11{{no matching function for call to 'cmp_greater'}}
+  std::cmp_greater(T(), int()); // expected-error11{{no matching function for call to 'cmp_greater'}}
+  std::cmp_greater(int(), T()); // expected-error11{{no matching function for call to 'cmp_greater'}}
+  std::cmp_greater_equal(T(), T()); // expected-error11{{no matching function for call to 'cmp_greater_equal'}}
+  std::cmp_greater_equal(T(), int()); // expected-error11{{no matching function for call to 'cmp_greater_equal'}}
+  std::cmp_greater_equal(int(), T()); // expected-error11{{no matching function for call to 'cmp_greater_equal'}}
+  std::in_range<T>(int()); // expected-error11{{no matching function for call to 'in_range'}}
+  std::in_range<int>(T()); // expected-error11{{no matching function for call to 'in_range'}}
+}
+#ifndef _LIBCPP_NO_HAS_CHAR8_T
+template <class T>
+constexpr void test_char8t() {
+  std::cmp_equal(T(), T()); // expected-error1{{no matching function for call to 'cmp_equal'}}
+  std::cmp_equal(T(), int()); // expected-error1{{no matching function for call to 'cmp_equal'}}
+  std::cmp_equal(int(), T()); // expected-error1{{no matching function for call to 'cmp_equal'}}
+  std::cmp_not_equal(T(), T()); // expected-error1{{no matching function for call to 'cmp_not_equal'}}
+  std::cmp_not_equal(T(), int()); // expected-error1{{no matching function for call to 'cmp_not_equal'}}
+  std::cmp_not_equal(int(), T()); // expected-error1{{no matching function for call to 'cmp_not_equal'}}
+  std::cmp_less(T(), T()); // expected-error1{{no matching function for call to 'cmp_less'}}
+  std::cmp_less(T(), int()); // expected-error1{{no matching function for call to 'cmp_less'}}
+  std::cmp_less(int(), T()); // expected-error1{{no matching function for call to 'cmp_less'}}
+  std::cmp_less_equal(T(), T()); // expected-error1{{no matching function for call to 'cmp_less_equal'}}
+  std::cmp_less_equal(T(), int()); // expected-error1{{no matching function for call to 'cmp_less_equal'}}
+  std::cmp_less_equal(int(), T()); // expected-error1{{no matching function for call to 'cmp_less_equal'}}
+  std::cmp_greater(T(), T()); // expected-error1{{no matching function for call to 'cmp_greater'}}
+  std::cmp_greater(T(), int()); // expected-error1{{no matching function for call to 'cmp_greater'}}
+  std::cmp_greater(int(), T()); // expected-error1{{no matching function for call to 'cmp_greater'}}
+  std::cmp_greater_equal(T(), T()); // expected-error1{{no matching function for call to 'cmp_greater_equal'}}
+  std::cmp_greater_equal(T(), int()); // expected-error1{{no matching function for call to 'cmp_greater_equal'}}
+  std::cmp_greater_equal(int(), T()); // expected-error1{{no matching function for call to 'cmp_greater_equal'}}
+  std::in_range<T>(int()); // expected-error1{{no matching function for call to 'in_range'}}
+  std::in_range<int>(T()); // expected-error1{{no matching function for call to 'in_range'}}
+}
+#endif // _LIBCPP_NO_HAS_CHAR8_T
+
+#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS
+template <class T>
+constexpr void test_uchars() {
+  std::cmp_equal(T(), T()); // expected-error2{{no matching function for call to 'cmp_equal'}}
+  std::cmp_equal(T(), int()); // expected-error2{{no matching function for call to 'cmp_equal'}}
+  std::cmp_equal(int(), T()); // expected-error2{{no matching function for call to 'cmp_equal'}}
+  std::cmp_not_equal(T(), T()); // expected-error2{{no matching function for call to 'cmp_not_equal'}}
+  std::cmp_not_equal(T(), int()); // expected-error2{{no matching function for call to 'cmp_not_equal'}}
+  std::cmp_not_equal(int(), T()); // expected-error2{{no matching function for call to 'cmp_not_equal'}}
+  std::cmp_less(T(), T()); // expected-error2{{no matching function for call to 'cmp_less'}}
+  std::cmp_less(T(), int()); // expected-error2{{no matching function for call to 'cmp_less'}}
+  std::cmp_less(int(), T()); // expected-error2{{no matching function for call to 'cmp_less'}}
+  std::cmp_less_equal(T(), T()); // expected-error2{{no matching function for call to 'cmp_less_equal'}}
+  std::cmp_less_equal(T(), int()); // expected-error2{{no matching function for call to 'cmp_less_equal'}}
+  std::cmp_less_equal(int(), T()); // expected-error2{{no matching function for call to 'cmp_less_equal'}}
+  std::cmp_greater(T(), T()); // expected-error2{{no matching function for call to 'cmp_greater'}}
+  std::cmp_greater(T(), int()); // expected-error2{{no matching function for call to 'cmp_greater'}}
+  std::cmp_greater(int(), T()); // expected-error2{{no matching function for call to 'cmp_greater'}}
+  std::cmp_greater_equal(T(), T()); // expected-error2{{no matching function for call to 'cmp_greater_equal'}}
+  std::cmp_greater_equal(T(), int()); // expected-error2{{no matching function for call to 'cmp_greater_equal'}}
+  std::cmp_greater_equal(int(), T()); // expected-error2{{no matching function for call to 'cmp_greater_equal'}}
+  std::in_range<T>(int()); // expected-error2{{no matching function for call to 'in_range'}}
+  std::in_range<int>(T()); // expected-error2{{no matching function for call to 'in_range'}}
+}
+#endif // _LIBCPP_HAS_NO_UNICODE_CHARS
+
+int main() {
+  test<bool>();
+  test<char>();
+  test<wchar_t>();
+  test<float>();
+  test<double>();
+  test<long double>();
+  test<std::byte>();
+  test<NonEmptyT>();
+  test<ColorT>();
+  test<nullptr_t>();
+  test<EmptyT>();
+#ifndef _LIBCPP_NO_HAS_CHAR8_T
+  test_char8t<char8_t>();
+#endif // !_LIBCPP_NO_HAS_CHAR8_T
+
+#ifndef _LIBCPP_HAS_NO_UNICODE_CHARS
+  test_uchars<char16_t>();
+  test_uchars<char32_t>();
+#endif // !_LIBCPP_HAS_NO_UNICODE_CHARS
+  return 0;
+}
diff --git a/test/std/utilities/utility/utility.intcmp/intcmp.in_range/in_range.pass.cpp b/test/std/utilities/utility/utility.intcmp/intcmp.in_range/in_range.pass.cpp
new file mode 100644
index 0000000..3946904
--- /dev/null
+++ b/test/std/utilities/utility/utility.intcmp/intcmp.in_range/in_range.pass.cpp
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// <utility>
+
+// template<class R, class T>
+//   constexpr bool in_range(T t) noexcept;               // C++20
+
+#include <utility>
+#include <limits>
+#include <numeric>
+#include <tuple>
+#include <cassert>
+
+#include "test_macros.h"
+
+template <typename T>
+struct Tuple {
+  T min;
+  T max;
+  T mid;
+  constexpr Tuple() {
+    min = std::numeric_limits<T>::min();
+    max = std::numeric_limits<T>::max();
+    mid = std::midpoint(min, max);
+  }
+};
+
+template <typename T>
+constexpr void test_in_range1() {
+  constexpr Tuple<T> tup;
+  assert(std::in_range<T>(tup.min));
+  assert(std::in_range<T>(tup.min + 1));
+  assert(std::in_range<T>(tup.max));
+  assert(std::in_range<T>(tup.max - 1));
+  assert(std::in_range<T>(tup.mid));
+  assert(std::in_range<T>(tup.mid - 1));
+  assert(std::in_range<T>(tup.mid + 1));
+}
+
+constexpr void test_in_range() {
+  constexpr Tuple<uint8_t> utup8;
+  constexpr Tuple<int8_t> stup8;
+  assert(!std::in_range<int8_t>(utup8.max));
+  assert(std::in_range<short>(utup8.max));
+  assert(!std::in_range<uint8_t>(stup8.min));
+  assert(std::in_range<int8_t>(utup8.mid));
+  assert(!std::in_range<uint8_t>(stup8.mid));
+  assert(!std::in_range<uint8_t>(-1));
+}
+
+template <class... Ts>
+constexpr void test1(const std::tuple<Ts...>&) {
+  (test_in_range1<Ts>() , ...);
+}
+
+constexpr bool test() {
+  std::tuple<
+#ifndef _LIBCPP_HAS_NO_INT128
+      __int128_t, __uint128_t,
+#endif
+      unsigned long long, long long, unsigned long, long, unsigned int, int,
+      unsigned short, short, unsigned char, signed char> types;
+  test1(types);
+  test_in_range();
+  return true;
+}
+
+int main() {
+  ASSERT_NOEXCEPT(std::in_range<int>(-1));
+  test();
+  static_assert(test());
+  return 0;
+}
diff --git a/utils/generate_feature_test_macro_components.py b/utils/generate_feature_test_macro_components.py
index 02f9e00..978430c 100755
--- a/utils/generate_feature_test_macro_components.py
+++ b/utils/generate_feature_test_macro_components.py
@@ -340,7 +340,8 @@
     "name": "__cpp_lib_integer_comparison_functions",
     "values": { "c++20": 202002 },
     "headers": ["utility"],
-    "unimplemented": True,
+    "test_suite_guard": "defined(__cpp_concepts) && __cpp_concepts >= 201907L",
+    "libcxx_guard": "!defined(_LIBCPP_HAS_NO_CONCEPTS)",
   }, {
     "name": "__cpp_lib_integer_sequence",
     "values": { "c++14": 201304 },