Index: libcxx/include/__ranges/empty.h
===================================================================
--- libcxx/include/__ranges/empty.h
+++ libcxx/include/__ranges/empty.h
@@ -9,11 +9,13 @@
 #ifndef _LIBCPP___RANGES_EMPTY_H
 #define _LIBCPP___RANGES_EMPTY_H
 
+#include <__concepts/class_or_enum.h>
 #include <__config>
 #include <__iterator/concepts.h>
 #include <__ranges/access.h>
 #include <__ranges/size.h>
 #include <__utility/forward.h>
+#include <__utility/priority_tag.h>
 #include <type_traits>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -24,47 +26,57 @@
 
 #if !defined(_LIBCPP_HAS_NO_RANGES)
 
-// clang-format off
-namespace ranges {
 // [range.prim.empty]
-namespace __empty {
-  template <class _Tp>
-  concept __member_empty = requires(_Tp&& __t) {
-    bool(_VSTD::forward<_Tp>(__t).empty());
-  };
-
-  template<class _Tp>
-  concept __can_invoke_size =
-    !__member_empty<_Tp> &&
-    requires(_Tp&& __t) { ranges::size(_VSTD::forward<_Tp>(__t)); };
 
+namespace ranges {
+namespace __empty {
   template <class _Tp>
-  concept __can_compare_begin_end =
-    !__member_empty<_Tp> &&
-    !__can_invoke_size<_Tp> &&
-    requires(_Tp&& __t) {
-      bool(ranges::begin(__t) == ranges::end(__t));
+  concept __basic_requirements =
+    requires (_Tp __t) {
+      requires __class_or_enum<remove_reference_t<_Tp>>;
+      bool(__t.empty());
+    } ||
+    requires (_Tp __t) { ranges::size(__t) == 0; } ||
+    requires (_Tp __t) {
       { ranges::begin(__t) } -> forward_iterator;
+      bool(ranges::begin(__t) == ranges::end(__t));
     };
 
   struct __fn {
-    template <__member_empty _Tp>
-    [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool operator()(_Tp&& __t) const
-        noexcept(noexcept(bool(__t.empty()))) {
-      return __t.empty();
-    }
+    template <class _Tp>
+      requires is_unbounded_array_v<remove_reference_t<_Tp>>
+    _LIBCPP_HIDE_FROM_ABI
+    static constexpr void __go(_Tp&& __t, __priority_tag<3>) = delete;
+
+    template <class _Tp, enable_if_t<__class_or_enum<remove_reference_t<_Tp>>>* = nullptr>
+    _LIBCPP_HIDE_FROM_ABI
+    static constexpr auto __go(_Tp&& __t, __priority_tag<2>)
+      noexcept(noexcept(bool(__t.empty())))
+      -> decltype(      bool(__t.empty()))
+      { return          bool(__t.empty()); }
+
+    template <class _Tp>
+    _LIBCPP_HIDE_FROM_ABI
+    static constexpr auto __go(_Tp&& __t, __priority_tag<1>)
+      noexcept(noexcept(ranges::size(__t) == 0))
+      -> decltype(      ranges::size(__t) == 0)
+      { return          ranges::size(__t) == 0; }
 
-    template <__can_invoke_size _Tp>
-    [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool operator()(_Tp&& __t) const
-        noexcept(noexcept(ranges::size(_VSTD::forward<_Tp>(__t)))) {
-      return ranges::size(_VSTD::forward<_Tp>(__t)) == 0;
-    }
+    template <class _Tp>
+    _LIBCPP_HIDE_FROM_ABI
+    static constexpr auto __go(_Tp&& __t, __priority_tag<0>)
+      noexcept(noexcept(bool(ranges::begin(__t) == ranges::end(__t))))
+      -> decltype(      bool(ranges::begin(__t) == ranges::end(__t)))
+      requires forward_iterator<decltype(ranges::begin(__t))>
+      { return          bool(ranges::begin(__t) == ranges::end(__t)); }
 
-    template<__can_compare_begin_end _Tp>
-    [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr bool operator()(_Tp&& __t) const
-        noexcept(noexcept(bool(ranges::begin(__t) == ranges::end(__t)))) {
-      return ranges::begin(__t) == ranges::end(__t);
-    }
+    template <class _Tp>
+    [[nodiscard]] _LIBCPP_HIDE_FROM_ABI
+    constexpr decltype(auto) operator()(_Tp&& __t) const
+      noexcept(noexcept(__go(_VSTD::forward<_Tp>(__t), __priority_tag<3>())))
+      requires __basic_requirements<_Tp&&> &&
+      (requires {       __go(_VSTD::forward<_Tp>(__t), __priority_tag<3>()); })
+      { return          __go(_VSTD::forward<_Tp>(__t), __priority_tag<3>()); }
   };
 }
 
@@ -72,7 +84,6 @@
   inline constexpr auto empty = __empty::__fn{};
 } // namespace __cpo
 } // namespace ranges
-// clang-format off
 
 #endif // !defined(_LIBCPP_HAS_NO_RANGES)
 
Index: libcxx/test/libcxx/ranges/range.access/empty.verify.cpp
===================================================================
--- /dev/null
+++ libcxx/test/libcxx/ranges/range.access/empty.verify.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// std::ranges::empty
+//   Substitution failure should give reasonably nice diagnostics.
+
+#include <ranges>
+
+void f() {
+  // expected-error@*:* {{no matching function for call}}
+  // expected-note@*:* {{does not satisfy '__class_or_enum'}}
+  // expected-note@*:* {{'ranges::size(__t) == 0' would be invalid}}
+  // expected-note@*:* {{'ranges::begin(__t)' would be invalid}}
+  std::ranges::empty(42);
+}
+
+void g() {
+  struct S {} s;
+  // expected-error@*:* {{no matching function for call}}
+  // expected-note@*:* {{'bool(__t.empty())' would be invalid}}
+  // expected-note@*:* {{'ranges::size(__t) == 0' would be invalid}}
+  // expected-note@*:* {{'ranges::begin(__t)' would be invalid}}
+  std::ranges::empty(s);
+}
Index: libcxx/test/libcxx/ranges/range.access/range.prim/empty.incomplete.verify.cpp
===================================================================
--- libcxx/test/libcxx/ranges/range.access/range.prim/empty.incomplete.verify.cpp
+++ /dev/null
@@ -1,53 +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
-// UNSUPPORTED: libcpp-no-concepts
-// UNSUPPORTED: libcpp-has-no-incomplete-ranges
-
-// Test the libc++ specific behavior that we provide a better diagnostic when calling
-// std::ranges::empty on an array of incomplete type.
-
-#include <ranges>
-
-struct Incomplete;
-
-void f(Incomplete arr[]) {
-  // expected-error@*:* {{is SFINAE-unfriendly on arrays of an incomplete type.}}
-  // expected-error@*:* {{call to deleted function call operator in type}}
-  // expected-error@*:* {{attempt to use a deleted function}}
-  std::ranges::begin(arr);
-}
-
-void f(Incomplete(&arr)[]) {
-  // expected-error@*:* {{is SFINAE-unfriendly on arrays of an incomplete type.}}
-  std::ranges::begin(arr);
-}
-
-void f(Incomplete(&&arr)[]) {
-  // expected-error@*:* {{is SFINAE-unfriendly on arrays of an incomplete type.}}
-  std::ranges::begin(arr);
-}
-
-void f2(Incomplete arr[2]) {
-  // expected-error@*:* {{call to deleted function call operator in type}}
-  // expected-error@*:* {{attempt to use a deleted function}}
-  std::ranges::begin(arr);
-}
-
-void f(Incomplete(&arr)[2]) {
-  std::ranges::begin(arr);
-}
-
-void f(Incomplete(&&arr)[2]) {
-  std::ranges::begin(arr);
-}
-
-void f(Incomplete(&arr)[2][2]) {
-  std::ranges::begin(arr);
-}
Index: libcxx/test/std/ranges/range.access/range.prim/empty.pass.cpp
===================================================================
--- libcxx/test/std/ranges/range.access/range.prim/empty.pass.cpp
+++ libcxx/test/std/ranges/range.access/range.prim/empty.pass.cpp
@@ -15,11 +15,11 @@
 #include <ranges>
 
 #include <cassert>
+#include <utility>
 #include "test_macros.h"
 #include "test_iterators.h"
 
 using RangeEmptyT = decltype(std::ranges::empty);
-using RangeSizeT = decltype(std::ranges::size);
 
 static_assert(!std::is_invocable_v<RangeEmptyT, int[]>);
 static_assert(!std::is_invocable_v<RangeEmptyT, int(&)[]>);
@@ -30,12 +30,29 @@
 static_assert( std::is_invocable_v<RangeEmptyT, int (&)[1]>);
 static_assert( std::is_invocable_v<RangeEmptyT, const int (&)[1]>);
 
-struct NonConstSizeAndEmpty {
-  int size();
+#if 0 // TODO
+struct Incomplete;
+static_assert(!std::is_invocable_v<RangeEmptyT, Incomplete[]>);
+static_assert(!std::is_invocable_v<RangeEmptyT, Incomplete(&)[]>);
+static_assert(!std::is_invocable_v<RangeEmptyT, Incomplete(&&)[]>);
+
+extern Incomplete array_of_incomplete[42];
+static_assert(!std::ranges::empty(array_of_incomplete));
+static_assert(!std::ranges::empty(std::move(array_of_incomplete)));
+static_assert(!std::ranges::empty(std::as_const(array_of_incomplete)));
+static_assert(!std::ranges::empty(static_cast<const Incomplete(&&)[42]>(array_of_incomplete)));
+#endif
+
+struct InputRangeWithoutSize {
+    cpp17_input_iterator<int*> begin() const;
+    cpp17_input_iterator<int*> end() const;
+};
+static_assert(!std::is_invocable_v<RangeEmptyT, const InputRangeWithoutSize&>);
+
+struct NonConstEmpty {
   bool empty();
 };
-static_assert(!std::is_invocable_v<RangeSizeT, const NonConstSizeAndEmpty&>);
-static_assert(!std::is_invocable_v<RangeEmptyT, const NonConstSizeAndEmpty&>);
+static_assert(!std::is_invocable_v<RangeEmptyT, const NonConstEmpty&>);
 
 struct HasMemberAndFunction {
   constexpr bool empty() const { return true; }
@@ -49,7 +66,7 @@
 static_assert(!std::is_invocable_v<RangeEmptyT, BadReturnType&>);
 
 struct BoolConvertible {
-  constexpr /*TODO: explicit*/ operator bool() noexcept(false) { return true; }
+  constexpr explicit operator bool() noexcept(false) { return true; }
 };
 struct BoolConvertibleReturnType {
   constexpr BoolConvertible empty() noexcept { return {}; }
@@ -148,9 +165,7 @@
   assert(std::ranges::empty(e) == false); // e.empty()
   assert(std::ranges::empty(std::as_const(e)) == true); // e.begin() == e.end()
 
-#if 0 // TODO FIXME
   assert(std::ranges::empty(EvilBeginEnd()));
-#endif
 
   return true;
 }