[libcxx-commits] [libcxx] 55bd22f - [libc++][ranges] Implement rbegin, rend, crbegin and crend.

Konstantin Varlamov via libcxx-commits libcxx-commits at lists.llvm.org
Mon Feb 14 03:30:14 PST 2022


Author: Konstantin Varlamov
Date: 2022-02-14T03:29:59-08:00
New Revision: 55bd22f853d8acbfabdfd1086fa4f4d97db71f63

URL: https://github.com/llvm/llvm-project/commit/55bd22f853d8acbfabdfd1086fa4f4d97db71f63
DIFF: https://github.com/llvm/llvm-project/commit/55bd22f853d8acbfabdfd1086fa4f4d97db71f63.diff

LOG: [libc++][ranges] Implement rbegin, rend, crbegin and crend.

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

Added: 
    libcxx/include/__ranges/rbegin.h
    libcxx/include/__ranges/rend.h
    libcxx/test/libcxx/diagnostics/detail.headers/ranges/rbegin.module.verify.cpp
    libcxx/test/libcxx/diagnostics/detail.headers/ranges/rend.module.verify.cpp
    libcxx/test/std/ranges/range.access/rbegin.pass.cpp
    libcxx/test/std/ranges/range.access/rend.pass.cpp

Modified: 
    libcxx/docs/Status/RangesPaper.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__concepts/class_or_enum.h
    libcxx/include/__ranges/access.h
    libcxx/include/module.modulemap
    libcxx/include/ranges

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/Status/RangesPaper.csv b/libcxx/docs/Status/RangesPaper.csv
index bbb63d42b1333..0aa3504ca083e 100644
--- a/libcxx/docs/Status/RangesPaper.csv
+++ b/libcxx/docs/Status/RangesPaper.csv
@@ -100,15 +100,15 @@ Section,Description,Dependencies,Assignee,Complete
 | `ranges::end <https://llvm.org/D100255>`_
 | `range::cbegin <https://llvm.org/D100255>`_
 | `ranges::cend <https://llvm.org/D100255>`_
-| ranges::rbegin
-| ranges::rend
-| ranges::crbegin
-| ranges::crend
+| `ranges::rbegin <https://llvm.org/D119057>`_
+| `ranges::rend <https://llvm.org/D119057>`_
+| `ranges::crbegin <https://llvm.org/D119057>`_
+| `ranges::crend <https://llvm.org/D119057>`_
 | `ranges::size <https://llvm.org/D101079>`_
 | `ranges::ssize <https://llvm.org/D101189>`_
 | `ranges::empty <https://llvm.org/D101193>`_
 | `ranges::data <https://llvm.org/D101476>`_
-| `ranges::cdata <https://llvm.org/D117044>`_",[iterator.concepts],Christopher Di Bella and Zoe Carver,In progress
+| `ranges::cdata <https://llvm.org/D117044>`_",[iterator.concepts],Various,✅
 `[range.range] <https://wg21.link/range.range>`_,"| `ranges::range <https://llvm.org/D100269>`_
 | `ranges::borrowed_range <https://llvm.org/D102426>`_
 | `ranges::enable_borrowed_range <https://llvm.org/D90999>`_

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 877befc6b96b3..eff6521b74b42 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -346,7 +346,9 @@ set(files
   __ranges/non_propagating_cache.h
   __ranges/owning_view.h
   __ranges/range_adaptor.h
+  __ranges/rbegin.h
   __ranges/ref_view.h
+  __ranges/rend.h
   __ranges/reverse_view.h
   __ranges/single_view.h
   __ranges/size.h

diff  --git a/libcxx/include/__concepts/class_or_enum.h b/libcxx/include/__concepts/class_or_enum.h
index f53d8dd074c9c..729e444b39ada 100644
--- a/libcxx/include/__concepts/class_or_enum.h
+++ b/libcxx/include/__concepts/class_or_enum.h
@@ -26,6 +26,7 @@ template<class _Tp>
 concept __class_or_enum = is_class_v<_Tp> || is_union_v<_Tp> || is_enum_v<_Tp>;
 
 // Work around Clang bug https://llvm.org/PR52970
+// TODO: remove this workaround once libc++ no longer has to support Clang 13 (it was fixed in Clang 14).
 template<class _Tp>
 concept __workaround_52970 = is_class_v<__uncvref_t<_Tp>> || is_union_v<__uncvref_t<_Tp>>;
 

diff  --git a/libcxx/include/__ranges/access.h b/libcxx/include/__ranges/access.h
index 5b623c1e4a2a8..2ebdab4eb8cd9 100644
--- a/libcxx/include/__ranges/access.h
+++ b/libcxx/include/__ranges/access.h
@@ -15,7 +15,6 @@
 #include <__iterator/readable_traits.h>
 #include <__ranges/enable_borrowed_range.h>
 #include <__utility/auto_cast.h>
-#include <concepts>
 #include <type_traits>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)

diff  --git a/libcxx/include/__ranges/rbegin.h b/libcxx/include/__ranges/rbegin.h
new file mode 100644
index 0000000000000..cc4c0582cc246
--- /dev/null
+++ b/libcxx/include/__ranges/rbegin.h
@@ -0,0 +1,130 @@
+// -*- 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___RANGES_RBEGIN_H
+#define _LIBCPP___RANGES_RBEGIN_H
+
+#include <__concepts/class_or_enum.h>
+#include <__concepts/same_as.h>
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__iterator/readable_traits.h>
+#include <__iterator/reverse_iterator.h>
+#include <__ranges/access.h>
+#include <__utility/auto_cast.h>
+#include <type_traits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+// [ranges.access.rbegin]
+
+namespace ranges {
+namespace __rbegin {
+template <class _Tp>
+concept __member_rbegin =
+  __can_borrow<_Tp> &&
+  __workaround_52970<_Tp> &&
+  requires(_Tp&& __t) {
+    { _LIBCPP_AUTO_CAST(__t.rbegin()) } -> input_or_output_iterator;
+  };
+
+void rbegin(auto&) = delete;
+void rbegin(const auto&) = delete;
+
+template <class _Tp>
+concept __unqualified_rbegin =
+  !__member_rbegin<_Tp> &&
+  __can_borrow<_Tp> &&
+  __class_or_enum<remove_cvref_t<_Tp>> &&
+  requires(_Tp&& __t) {
+    { _LIBCPP_AUTO_CAST(rbegin(__t)) } -> input_or_output_iterator;
+  };
+
+template <class _Tp>
+concept __can_reverse =
+  __can_borrow<_Tp> &&
+  !__member_rbegin<_Tp> &&
+  !__unqualified_rbegin<_Tp> &&
+  requires(_Tp&& __t) {
+    { ranges::begin(__t) } -> same_as<decltype(ranges::end(__t))>;
+    { ranges::begin(__t) } -> bidirectional_iterator;
+  };
+
+struct __fn {
+  template <class _Tp>
+    requires __member_rbegin<_Tp>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
+    noexcept(noexcept(_LIBCPP_AUTO_CAST(__t.rbegin())))
+  {
+    return _LIBCPP_AUTO_CAST(__t.rbegin());
+  }
+
+  template <class _Tp>
+    requires __unqualified_rbegin<_Tp>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
+    noexcept(noexcept(_LIBCPP_AUTO_CAST(rbegin(__t))))
+  {
+    return _LIBCPP_AUTO_CAST(rbegin(__t));
+  }
+
+  template <class _Tp>
+    requires __can_reverse<_Tp>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
+    noexcept(noexcept(ranges::end(__t)))
+  {
+    return std::make_reverse_iterator(ranges::end(__t));
+  }
+
+  void operator()(auto&&) const = delete;
+};
+} // namespace __rbegin
+
+inline namespace __cpo {
+  inline constexpr auto rbegin = __rbegin::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+// [range.access.crbegin]
+
+namespace ranges {
+namespace __crbegin {
+struct __fn {
+  template <class _Tp>
+    requires is_lvalue_reference_v<_Tp&&>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI
+  constexpr auto operator()(_Tp&& __t) const
+    noexcept(noexcept(ranges::rbegin(static_cast<const remove_reference_t<_Tp>&>(__t))))
+    -> decltype(      ranges::rbegin(static_cast<const remove_reference_t<_Tp>&>(__t)))
+    { return          ranges::rbegin(static_cast<const remove_reference_t<_Tp>&>(__t)); }
+
+  template <class _Tp>
+    requires is_rvalue_reference_v<_Tp&&>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI
+  constexpr auto operator()(_Tp&& __t) const
+    noexcept(noexcept(ranges::rbegin(static_cast<const _Tp&&>(__t))))
+    -> decltype(      ranges::rbegin(static_cast<const _Tp&&>(__t)))
+    { return          ranges::rbegin(static_cast<const _Tp&&>(__t)); }
+};
+} // namespace __crbegin
+
+inline namespace __cpo {
+  inline constexpr auto crbegin = __crbegin::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___RANGES_RBEGIN_H

diff  --git a/libcxx/include/__ranges/rend.h b/libcxx/include/__ranges/rend.h
new file mode 100644
index 0000000000000..cd7826021d446
--- /dev/null
+++ b/libcxx/include/__ranges/rend.h
@@ -0,0 +1,134 @@
+// -*- 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___RANGES_REND_H
+#define _LIBCPP___RANGES_REND_H
+
+#include <__concepts/class_or_enum.h>
+#include <__concepts/same_as.h>
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__iterator/readable_traits.h>
+#include <__iterator/reverse_iterator.h>
+#include <__ranges/access.h>
+#include <__ranges/rbegin.h>
+#include <__utility/auto_cast.h>
+#include <type_traits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+// [range.access.rend]
+
+namespace ranges {
+namespace __rend {
+template <class _Tp>
+concept __member_rend =
+  __can_borrow<_Tp> &&
+  __workaround_52970<_Tp> &&
+  requires(_Tp&& __t) {
+    ranges::rbegin(__t);
+    { _LIBCPP_AUTO_CAST(__t.rend()) } -> sentinel_for<decltype(ranges::rbegin(__t))>;
+  };
+
+void rend(auto&) = delete;
+void rend(const auto&) = delete;
+
+template <class _Tp>
+concept __unqualified_rend =
+  !__member_rend<_Tp> &&
+  __can_borrow<_Tp> &&
+  __class_or_enum<remove_cvref_t<_Tp>> &&
+  requires(_Tp&& __t) {
+    ranges::rbegin(__t);
+    { _LIBCPP_AUTO_CAST(rend(__t)) } -> sentinel_for<decltype(ranges::rbegin(__t))>;
+  };
+
+template <class _Tp>
+concept __can_reverse =
+  __can_borrow<_Tp> &&
+  !__member_rend<_Tp> &&
+  !__unqualified_rend<_Tp> &&
+  requires(_Tp&& __t) {
+    { ranges::begin(__t) } -> same_as<decltype(ranges::end(__t))>;
+    { ranges::begin(__t) } -> bidirectional_iterator;
+  };
+
+class __fn {
+public:
+  template <class _Tp>
+    requires __member_rend<_Tp>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
+    noexcept(noexcept(_LIBCPP_AUTO_CAST(__t.rend())))
+  {
+    return _LIBCPP_AUTO_CAST(__t.rend());
+  }
+
+  template <class _Tp>
+    requires __unqualified_rend<_Tp>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
+    noexcept(noexcept(_LIBCPP_AUTO_CAST(rend(__t))))
+  {
+    return _LIBCPP_AUTO_CAST(rend(__t));
+  }
+
+  template <class _Tp>
+    requires __can_reverse<_Tp>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Tp&& __t) const
+    noexcept(noexcept(ranges::begin(__t)))
+  {
+    return std::make_reverse_iterator(ranges::begin(__t));
+  }
+
+  void operator()(auto&&) const = delete;
+};
+} // namespace __rend
+
+inline namespace __cpo {
+  inline constexpr auto rend = __rend::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+// [range.access.crend]
+
+namespace ranges {
+namespace __crend {
+struct __fn {
+  template <class _Tp>
+    requires is_lvalue_reference_v<_Tp&&>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI
+  constexpr auto operator()(_Tp&& __t) const
+    noexcept(noexcept(ranges::rend(static_cast<const remove_reference_t<_Tp>&>(__t))))
+    -> decltype(      ranges::rend(static_cast<const remove_reference_t<_Tp>&>(__t)))
+    { return          ranges::rend(static_cast<const remove_reference_t<_Tp>&>(__t)); }
+
+  template <class _Tp>
+    requires is_rvalue_reference_v<_Tp&&>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI
+  constexpr auto operator()(_Tp&& __t) const
+    noexcept(noexcept(ranges::rend(static_cast<const _Tp&&>(__t))))
+    -> decltype(      ranges::rend(static_cast<const _Tp&&>(__t)))
+    { return          ranges::rend(static_cast<const _Tp&&>(__t)); }
+};
+} // namespace __crend
+
+inline namespace __cpo {
+  inline constexpr auto crend = __crend::__fn{};
+} // namespace __cpo
+} // namespace ranges
+
+#endif // !defined(_LIBCPP_HAS_NO_CONCEPTS)
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___RANGES_REND_H

diff  --git a/libcxx/include/module.modulemap b/libcxx/include/module.modulemap
index 572705330fe0d..0ef457afe33ad 100644
--- a/libcxx/include/module.modulemap
+++ b/libcxx/include/module.modulemap
@@ -815,7 +815,9 @@ module std [system] {
       module non_propagating_cache  { private header "__ranges/non_propagating_cache.h" }
       module owning_view            { private header "__ranges/owning_view.h" }
       module range_adaptor          { private header "__ranges/range_adaptor.h" }
+      module rbegin                 { private header "__ranges/rbegin.h" }
       module ref_view               { private header "__ranges/ref_view.h" }
+      module rend                   { private header "__ranges/rend.h" }
       module reverse_view           { private header "__ranges/reverse_view.h" }
       module single_view            { private header "__ranges/single_view.h" }
       module size                   { private header "__ranges/size.h" }

diff  --git a/libcxx/include/ranges b/libcxx/include/ranges
index e8142084fe975..2bc9121cd68b4 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -250,7 +250,9 @@ namespace std {
 #include <__ranges/enable_view.h>
 #include <__ranges/iota_view.h>
 #include <__ranges/join_view.h>
+#include <__ranges/rbegin.h>
 #include <__ranges/ref_view.h>
+#include <__ranges/rend.h>
 #include <__ranges/reverse_view.h>
 #include <__ranges/single_view.h>
 #include <__ranges/size.h>

diff  --git a/libcxx/test/libcxx/diagnostics/detail.headers/ranges/rbegin.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/ranges/rbegin.module.verify.cpp
new file mode 100644
index 0000000000000..c91765bba5657
--- /dev/null
+++ b/libcxx/test/libcxx/diagnostics/detail.headers/ranges/rbegin.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: '__ranges/rbegin.h'}}
+#include <__ranges/rbegin.h>

diff  --git a/libcxx/test/libcxx/diagnostics/detail.headers/ranges/rend.module.verify.cpp b/libcxx/test/libcxx/diagnostics/detail.headers/ranges/rend.module.verify.cpp
new file mode 100644
index 0000000000000..460c5a9594a91
--- /dev/null
+++ b/libcxx/test/libcxx/diagnostics/detail.headers/ranges/rend.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: '__ranges/rend.h'}}
+#include <__ranges/rend.h>

diff  --git a/libcxx/test/std/ranges/range.access/rbegin.pass.cpp b/libcxx/test/std/ranges/range.access/rbegin.pass.cpp
new file mode 100644
index 0000000000000..47c65f4e1d7f7
--- /dev/null
+++ b/libcxx/test/std/ranges/range.access/rbegin.pass.cpp
@@ -0,0 +1,522 @@
+//===----------------------------------------------------------------------===//
+//
+// 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::rbegin
+// std::ranges::crbegin
+
+#include <ranges>
+
+#include <cassert>
+#include <utility>
+#include "test_macros.h"
+#include "test_iterators.h"
+
+using RangeRBeginT = decltype(std::ranges::rbegin);
+using RangeCRBeginT = decltype(std::ranges::crbegin);
+
+static int globalBuff[8];
+
+static_assert(!std::is_invocable_v<RangeRBeginT, int (&&)[10]>);
+static_assert( std::is_invocable_v<RangeRBeginT, int (&)[10]>);
+static_assert(!std::is_invocable_v<RangeRBeginT, int (&&)[]>);
+static_assert(!std::is_invocable_v<RangeRBeginT, int (&)[]>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, int (&&)[10]>);
+static_assert( std::is_invocable_v<RangeCRBeginT, int (&)[10]>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, int (&&)[]>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, int (&)[]>);
+
+struct Incomplete;
+
+static_assert(!std::is_invocable_v<RangeRBeginT, Incomplete(&&)[]>);
+static_assert(!std::is_invocable_v<RangeRBeginT, const Incomplete(&&)[]>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, Incomplete(&&)[]>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, const Incomplete(&&)[]>);
+
+static_assert(!std::is_invocable_v<RangeRBeginT, Incomplete(&&)[10]>);
+static_assert(!std::is_invocable_v<RangeRBeginT, const Incomplete(&&)[10]>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, Incomplete(&&)[10]>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, const Incomplete(&&)[10]>);
+
+// This case is IFNDR; we handle it SFINAE-friendly.
+LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeRBeginT, Incomplete(&)[]>);
+LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeRBeginT, const Incomplete(&)[]>);
+LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCRBeginT, Incomplete(&)[]>);
+LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCRBeginT, const Incomplete(&)[]>);
+
+// This case is IFNDR; we handle it SFINAE-friendly.
+LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeRBeginT, Incomplete(&)[10]>);
+LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeRBeginT, const Incomplete(&)[10]>);
+LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCRBeginT, Incomplete(&)[10]>);
+LIBCPP_STATIC_ASSERT(!std::is_invocable_v<RangeCRBeginT, const Incomplete(&)[10]>);
+
+struct RBeginMember {
+  int x;
+  constexpr const int *rbegin() const { return &x; }
+};
+
+// Ensure that we can't call with rvalues with borrowing disabled.
+static_assert( std::is_invocable_v<RangeRBeginT, RBeginMember &>);
+static_assert(!std::is_invocable_v<RangeRBeginT, RBeginMember &&>);
+static_assert( std::is_invocable_v<RangeRBeginT, RBeginMember const&>);
+static_assert(!std::is_invocable_v<RangeRBeginT, RBeginMember const&&>);
+static_assert( std::is_invocable_v<RangeCRBeginT, RBeginMember &>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, RBeginMember &&>);
+static_assert( std::is_invocable_v<RangeCRBeginT, RBeginMember const&>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, RBeginMember const&&>);
+
+constexpr bool testReturnTypes() {
+  {
+    int *x[2];
+    ASSERT_SAME_TYPE(decltype(std::ranges::rbegin(x)), std::reverse_iterator<int**>);
+    ASSERT_SAME_TYPE(decltype(std::ranges::crbegin(x)), std::reverse_iterator<int* const*>);
+  }
+  {
+    int x[2][2];
+    ASSERT_SAME_TYPE(decltype(std::ranges::rbegin(x)), std::reverse_iterator<int(*)[2]>);
+    ASSERT_SAME_TYPE(decltype(std::ranges::crbegin(x)), std::reverse_iterator<const int(*)[2]>);
+  }
+  {
+    struct Different {
+      char*& rbegin();
+      short*& rbegin() const;
+    } x;
+    ASSERT_SAME_TYPE(decltype(std::ranges::rbegin(x)), char*);
+    ASSERT_SAME_TYPE(decltype(std::ranges::crbegin(x)), short*);
+  }
+  return true;
+}
+
+constexpr bool testArray() {
+  int a[2];
+  assert(std::ranges::rbegin(a).base() == a + 2);
+  assert(std::ranges::crbegin(a).base() == a + 2);
+
+  int b[2][2];
+  assert(std::ranges::rbegin(b).base() == b + 2);
+  assert(std::ranges::crbegin(b).base() == b + 2);
+
+  RBeginMember c[2];
+  assert(std::ranges::rbegin(c).base() == c + 2);
+  assert(std::ranges::crbegin(c).base() == c + 2);
+
+  return true;
+}
+
+struct RBeginMemberReturnsInt {
+  int rbegin() const;
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, RBeginMemberReturnsInt const&>);
+
+struct RBeginMemberReturnsVoidPtr {
+  const void *rbegin() const;
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, RBeginMemberReturnsVoidPtr const&>);
+
+struct PtrConvertibleRBeginMember {
+  struct iterator { operator int*() const; };
+  iterator rbegin() const;
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, PtrConvertibleRBeginMember const&>);
+
+struct NonConstRBeginMember {
+  int x;
+  constexpr int* rbegin() { return &x; }
+};
+static_assert( std::is_invocable_v<RangeRBeginT,  NonConstRBeginMember &>);
+static_assert(!std::is_invocable_v<RangeRBeginT,  NonConstRBeginMember const&>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, NonConstRBeginMember &>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, NonConstRBeginMember const&>);
+
+struct EnabledBorrowingRBeginMember {
+  constexpr int *rbegin() const { return globalBuff; }
+};
+template<>
+inline constexpr bool std::ranges::enable_borrowed_range<EnabledBorrowingRBeginMember> = true;
+
+struct RBeginMemberFunction {
+  int x;
+  constexpr const int *rbegin() const { return &x; }
+  friend int* rbegin(RBeginMemberFunction const&);
+};
+
+struct EmptyPtrRBeginMember {
+  struct Empty {};
+  Empty x;
+  constexpr const Empty* rbegin() const { return &x; }
+};
+
+constexpr bool testRBeginMember() {
+  RBeginMember a;
+  assert(std::ranges::rbegin(a) == &a.x);
+  assert(std::ranges::crbegin(a) == &a.x);
+  static_assert(!std::is_invocable_v<RangeRBeginT, RBeginMember&&>);
+  static_assert(!std::is_invocable_v<RangeCRBeginT, RBeginMember&&>);
+
+  NonConstRBeginMember b;
+  assert(std::ranges::rbegin(b) == &b.x);
+  static_assert(!std::is_invocable_v<RangeCRBeginT, NonConstRBeginMember&>);
+
+  EnabledBorrowingRBeginMember c;
+  assert(std::ranges::rbegin(c) == globalBuff);
+  assert(std::ranges::crbegin(c) == globalBuff);
+  assert(std::ranges::rbegin(std::move(c)) == globalBuff);
+  assert(std::ranges::crbegin(std::move(c)) == globalBuff);
+
+  RBeginMemberFunction d;
+  assert(std::ranges::rbegin(d) == &d.x);
+  assert(std::ranges::crbegin(d) == &d.x);
+
+  EmptyPtrRBeginMember e;
+  assert(std::ranges::rbegin(e) == &e.x);
+  assert(std::ranges::crbegin(e) == &e.x);
+
+  return true;
+}
+
+
+struct RBeginFunction {
+  int x;
+  friend constexpr const int* rbegin(RBeginFunction const& bf) { return &bf.x; }
+};
+static_assert( std::is_invocable_v<RangeRBeginT,  RBeginFunction const&>);
+static_assert(!std::is_invocable_v<RangeRBeginT,  RBeginFunction &&>);
+static_assert(!std::is_invocable_v<RangeRBeginT,  RBeginFunction &>);
+static_assert( std::is_invocable_v<RangeCRBeginT, RBeginFunction const&>);
+static_assert( std::is_invocable_v<RangeCRBeginT, RBeginFunction &>);
+
+struct RBeginFunctionReturnsInt {
+  friend int rbegin(RBeginFunctionReturnsInt const&);
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, RBeginFunctionReturnsInt const&>);
+
+struct RBeginFunctionReturnsVoidPtr {
+  friend void *rbegin(RBeginFunctionReturnsVoidPtr const&);
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, RBeginFunctionReturnsVoidPtr const&>);
+
+struct RBeginFunctionReturnsEmpty {
+  struct Empty {};
+  friend Empty rbegin(RBeginFunctionReturnsEmpty const&);
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, RBeginFunctionReturnsEmpty const&>);
+
+struct RBeginFunctionReturnsPtrConvertible {
+  struct iterator { operator int*() const; };
+  friend iterator rbegin(RBeginFunctionReturnsPtrConvertible const&);
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, RBeginFunctionReturnsPtrConvertible const&>);
+
+struct RBeginFunctionByValue {
+  friend constexpr int *rbegin(RBeginFunctionByValue) { return globalBuff + 1; }
+};
+static_assert(!std::is_invocable_v<RangeCRBeginT, RBeginFunctionByValue>);
+
+struct RBeginFunctionEnabledBorrowing {
+  friend constexpr int *rbegin(RBeginFunctionEnabledBorrowing) { return globalBuff + 2; }
+};
+template<>
+inline constexpr bool std::ranges::enable_borrowed_range<RBeginFunctionEnabledBorrowing> = true;
+
+struct RBeginFunctionReturnsEmptyPtr {
+  struct Empty {};
+  Empty x;
+  friend constexpr const Empty *rbegin(RBeginFunctionReturnsEmptyPtr const& bf) { return &bf.x; }
+};
+
+struct RBeginFunctionWithDataMember {
+  int x;
+  int rbegin;
+  friend constexpr const int *rbegin(RBeginFunctionWithDataMember const& bf) { return &bf.x; }
+};
+
+struct RBeginFunctionWithPrivateBeginMember {
+  int y;
+  friend constexpr const int *rbegin(RBeginFunctionWithPrivateBeginMember const& bf) { return &bf.y; }
+private:
+  const int *rbegin() const;
+};
+
+constexpr bool testRBeginFunction() {
+  RBeginFunction a{};
+  const RBeginFunction aa{};
+  static_assert(!std::invocable<RangeRBeginT, decltype((a))>);
+  assert(std::ranges::crbegin(a) == &a.x);
+  assert(std::ranges::rbegin(aa) == &aa.x);
+  assert(std::ranges::crbegin(aa) == &aa.x);
+
+  RBeginFunctionByValue b{};
+  const RBeginFunctionByValue bb{};
+  assert(std::ranges::rbegin(b) == globalBuff + 1);
+  assert(std::ranges::crbegin(b) == globalBuff + 1);
+  assert(std::ranges::rbegin(bb) == globalBuff + 1);
+  assert(std::ranges::crbegin(bb) == globalBuff + 1);
+
+  RBeginFunctionEnabledBorrowing c{};
+  const RBeginFunctionEnabledBorrowing cc{};
+  assert(std::ranges::rbegin(std::move(c)) == globalBuff + 2);
+  assert(std::ranges::crbegin(std::move(c)) == globalBuff + 2);
+  assert(std::ranges::rbegin(std::move(cc)) == globalBuff + 2);
+  assert(std::ranges::crbegin(std::move(cc)) == globalBuff + 2);
+
+  RBeginFunctionReturnsEmptyPtr d{};
+  const RBeginFunctionReturnsEmptyPtr dd{};
+  static_assert(!std::invocable<RangeRBeginT, decltype((d))>);
+  assert(std::ranges::crbegin(d) == &d.x);
+  assert(std::ranges::rbegin(dd) == &dd.x);
+  assert(std::ranges::crbegin(dd) == &dd.x);
+
+  RBeginFunctionWithDataMember e{};
+  const RBeginFunctionWithDataMember ee{};
+  static_assert(!std::invocable<RangeRBeginT, decltype((e))>);
+  assert(std::ranges::rbegin(ee) == &ee.x);
+  assert(std::ranges::crbegin(e) == &e.x);
+  assert(std::ranges::crbegin(ee) == &ee.x);
+
+  RBeginFunctionWithPrivateBeginMember f{};
+  const RBeginFunctionWithPrivateBeginMember ff{};
+  static_assert(!std::invocable<RangeRBeginT, decltype((f))>);
+  assert(std::ranges::crbegin(f) == &f.y);
+  assert(std::ranges::rbegin(ff) == &ff.y);
+  assert(std::ranges::crbegin(ff) == &ff.y);
+
+  return true;
+}
+
+
+struct MemberBeginEnd {
+  int b, e;
+  char cb, ce;
+  constexpr bidirectional_iterator<int*> begin() { return bidirectional_iterator<int*>(&b); }
+  constexpr bidirectional_iterator<int*> end() { return bidirectional_iterator<int*>(&e); }
+  constexpr bidirectional_iterator<const char*> begin() const { return bidirectional_iterator<const char*>(&cb); }
+  constexpr bidirectional_iterator<const char*> end() const { return bidirectional_iterator<const char*>(&ce); }
+};
+static_assert( std::is_invocable_v<RangeRBeginT, MemberBeginEnd&>);
+static_assert( std::is_invocable_v<RangeRBeginT, MemberBeginEnd const&>);
+static_assert( std::is_invocable_v<RangeCRBeginT, MemberBeginEnd const&>);
+
+struct FunctionBeginEnd {
+  int b, e;
+  char cb, ce;
+  friend constexpr bidirectional_iterator<int*> begin(FunctionBeginEnd& v) {
+    return bidirectional_iterator<int*>(&v.b);
+  }
+  friend constexpr bidirectional_iterator<int*> end(FunctionBeginEnd& v) { return bidirectional_iterator<int*>(&v.e); }
+  friend constexpr bidirectional_iterator<const char*> begin(const FunctionBeginEnd& v) {
+    return bidirectional_iterator<const char*>(&v.cb);
+  }
+  friend constexpr bidirectional_iterator<const char*> end(const FunctionBeginEnd& v) {
+    return bidirectional_iterator<const char*>(&v.ce);
+  }
+};
+static_assert( std::is_invocable_v<RangeRBeginT, FunctionBeginEnd&>);
+static_assert( std::is_invocable_v<RangeRBeginT, FunctionBeginEnd const&>);
+static_assert( std::is_invocable_v<RangeCRBeginT, FunctionBeginEnd const&>);
+
+struct MemberBeginFunctionEnd {
+  int b, e;
+  char cb, ce;
+  constexpr bidirectional_iterator<int*> begin() { return bidirectional_iterator<int*>(&b); }
+  friend constexpr bidirectional_iterator<int*> end(MemberBeginFunctionEnd& v) {
+    return bidirectional_iterator<int*>(&v.e);
+  }
+  constexpr bidirectional_iterator<const char*> begin() const { return bidirectional_iterator<const char*>(&cb); }
+  friend constexpr bidirectional_iterator<const char*> end(const MemberBeginFunctionEnd& v) {
+    return bidirectional_iterator<const char*>(&v.ce);
+  }
+};
+static_assert( std::is_invocable_v<RangeRBeginT, MemberBeginFunctionEnd&>);
+static_assert( std::is_invocable_v<RangeRBeginT, MemberBeginFunctionEnd const&>);
+static_assert( std::is_invocable_v<RangeCRBeginT, MemberBeginFunctionEnd const&>);
+
+struct FunctionBeginMemberEnd {
+  int b, e;
+  char cb, ce;
+  friend constexpr bidirectional_iterator<int*> begin(FunctionBeginMemberEnd& v) {
+    return bidirectional_iterator<int*>(&v.b);
+  }
+  constexpr bidirectional_iterator<int*> end() { return bidirectional_iterator<int*>(&e); }
+  friend constexpr bidirectional_iterator<const char*> begin(const FunctionBeginMemberEnd& v) {
+    return bidirectional_iterator<const char*>(&v.cb);
+  }
+  constexpr bidirectional_iterator<const char*> end() const { return bidirectional_iterator<const char*>(&ce); }
+};
+static_assert( std::is_invocable_v<RangeRBeginT, FunctionBeginMemberEnd&>);
+static_assert( std::is_invocable_v<RangeRBeginT, FunctionBeginMemberEnd const&>);
+static_assert( std::is_invocable_v<RangeCRBeginT, FunctionBeginMemberEnd const&>);
+
+struct MemberBeginEndDifferentTypes {
+  bidirectional_iterator<int*> begin();
+  bidirectional_iterator<const int*> end();
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, MemberBeginEndDifferentTypes&>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, MemberBeginEndDifferentTypes&>);
+
+struct FunctionBeginEndDifferentTypes {
+  friend bidirectional_iterator<int*> begin(FunctionBeginEndDifferentTypes&);
+  friend bidirectional_iterator<const int*> end(FunctionBeginEndDifferentTypes&);
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, FunctionBeginEndDifferentTypes&>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, FunctionBeginEndDifferentTypes&>);
+
+struct MemberBeginEndForwardIterators {
+  forward_iterator<int*> begin();
+  forward_iterator<int*> end();
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, MemberBeginEndForwardIterators&>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, MemberBeginEndForwardIterators&>);
+
+struct FunctionBeginEndForwardIterators {
+  friend forward_iterator<int*> begin(FunctionBeginEndForwardIterators&);
+  friend forward_iterator<int*> end(FunctionBeginEndForwardIterators&);
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, FunctionBeginEndForwardIterators&>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, FunctionBeginEndForwardIterators&>);
+
+struct MemberBeginOnly {
+  bidirectional_iterator<int*> begin() const;
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, MemberBeginOnly&>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, MemberBeginOnly&>);
+
+struct FunctionBeginOnly {
+  friend bidirectional_iterator<int*> begin(FunctionBeginOnly&);
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, FunctionBeginOnly&>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, FunctionBeginOnly&>);
+
+struct MemberEndOnly {
+  bidirectional_iterator<int*> end() const;
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, MemberEndOnly&>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, MemberEndOnly&>);
+
+struct FunctionEndOnly {
+  friend bidirectional_iterator<int*> end(FunctionEndOnly&);
+};
+static_assert(!std::is_invocable_v<RangeRBeginT, FunctionEndOnly&>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, FunctionEndOnly&>);
+
+// Make sure there is no clash between the following cases:
+// - the case that handles classes defining member `rbegin` and `rend` functions;
+// - the case that handles classes defining `begin` and `end` functions returning reversible iterators.
+struct MemberBeginAndRBegin {
+  int* begin() const;
+  int* end() const;
+  int* rbegin() const;
+  int* rend() const;
+};
+static_assert( std::is_invocable_v<RangeRBeginT, MemberBeginAndRBegin&>);
+static_assert( std::is_invocable_v<RangeCRBeginT, MemberBeginAndRBegin&>);
+static_assert( std::same_as<std::invoke_result_t<RangeRBeginT, MemberBeginAndRBegin&>, int*>);
+static_assert( std::same_as<std::invoke_result_t<RangeCRBeginT, MemberBeginAndRBegin&>, int*>);
+
+constexpr bool testBeginEnd() {
+  MemberBeginEnd a{};
+  const MemberBeginEnd aa{};
+  assert(std::ranges::rbegin(a).base().base() == &a.e);
+  assert(std::ranges::crbegin(a).base().base() == &a.ce);
+  assert(std::ranges::rbegin(aa).base().base() == &aa.ce);
+  assert(std::ranges::crbegin(aa).base().base() == &aa.ce);
+
+  FunctionBeginEnd b{};
+  const FunctionBeginEnd bb{};
+  assert(std::ranges::rbegin(b).base().base() == &b.e);
+  assert(std::ranges::crbegin(b).base().base() == &b.ce);
+  assert(std::ranges::rbegin(bb).base().base() == &bb.ce);
+  assert(std::ranges::crbegin(bb).base().base() == &bb.ce);
+
+  MemberBeginFunctionEnd c{};
+  const MemberBeginFunctionEnd cc{};
+  assert(std::ranges::rbegin(c).base().base() == &c.e);
+  assert(std::ranges::crbegin(c).base().base() == &c.ce);
+  assert(std::ranges::rbegin(cc).base().base() == &cc.ce);
+  assert(std::ranges::crbegin(cc).base().base() == &cc.ce);
+
+  FunctionBeginMemberEnd d{};
+  const FunctionBeginMemberEnd dd{};
+  assert(std::ranges::rbegin(d).base().base() == &d.e);
+  assert(std::ranges::crbegin(d).base().base() == &d.ce);
+  assert(std::ranges::rbegin(dd).base().base() == &dd.ce);
+  assert(std::ranges::crbegin(dd).base().base() == &dd.ce);
+
+  return true;
+}
+
+
+ASSERT_NOEXCEPT(std::ranges::rbegin(std::declval<int (&)[10]>()));
+ASSERT_NOEXCEPT(std::ranges::crbegin(std::declval<int (&)[10]>()));
+
+struct NoThrowMemberRBegin {
+  ThrowingIterator<int> rbegin() const noexcept; // auto(t.rbegin()) doesn't throw
+} ntmb;
+static_assert(noexcept(std::ranges::rbegin(ntmb)));
+static_assert(noexcept(std::ranges::crbegin(ntmb)));
+
+struct NoThrowADLRBegin {
+  friend ThrowingIterator<int> rbegin(NoThrowADLRBegin&) noexcept;  // auto(rbegin(t)) doesn't throw
+  friend ThrowingIterator<int> rbegin(const NoThrowADLRBegin&) noexcept;
+} ntab;
+static_assert(noexcept(std::ranges::rbegin(ntab)));
+static_assert(noexcept(std::ranges::crbegin(ntab)));
+
+struct NoThrowMemberRBeginReturnsRef {
+  ThrowingIterator<int>& rbegin() const noexcept; // auto(t.rbegin()) may throw
+} ntmbrr;
+static_assert(!noexcept(std::ranges::rbegin(ntmbrr)));
+static_assert(!noexcept(std::ranges::crbegin(ntmbrr)));
+
+struct RBeginReturnsArrayRef {
+    auto rbegin() const noexcept -> int(&)[10];
+} brar;
+static_assert(noexcept(std::ranges::rbegin(brar)));
+static_assert(noexcept(std::ranges::crbegin(brar)));
+
+struct NoThrowBeginThrowingEnd {
+  int* begin() const noexcept;
+  int* end() const;
+} ntbte;
+static_assert(!noexcept(std::ranges::rbegin(ntbte)));
+static_assert(!noexcept(std::ranges::crbegin(ntbte)));
+
+struct NoThrowEndThrowingBegin {
+  int* begin() const;
+  int* end() const noexcept;
+} ntetb;
+static_assert(noexcept(std::ranges::rbegin(ntetb)));
+static_assert(noexcept(std::ranges::crbegin(ntetb)));
+
+// Test ADL-proofing.
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(!std::is_invocable_v<RangeRBeginT, Holder<Incomplete>*>);
+static_assert(!std::is_invocable_v<RangeRBeginT, Holder<Incomplete>*&>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, Holder<Incomplete>*>);
+static_assert(!std::is_invocable_v<RangeCRBeginT, Holder<Incomplete>*&>);
+
+int main(int, char**) {
+  static_assert(testReturnTypes());
+
+  testArray();
+  static_assert(testArray());
+
+  testRBeginMember();
+  static_assert(testRBeginMember());
+
+  testRBeginFunction();
+  static_assert(testRBeginFunction());
+
+  testBeginEnd();
+  static_assert(testBeginEnd());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.access/rend.pass.cpp b/libcxx/test/std/ranges/range.access/rend.pass.cpp
new file mode 100644
index 0000000000000..b15cec155b6ff
--- /dev/null
+++ b/libcxx/test/std/ranges/range.access/rend.pass.cpp
@@ -0,0 +1,551 @@
+//===----------------------------------------------------------------------===//
+//
+// 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::rend
+// std::ranges::crend
+
+#include <ranges>
+
+#include <cassert>
+#include <utility>
+#include "test_macros.h"
+#include "test_iterators.h"
+
+using RangeREndT = decltype(std::ranges::rend);
+using RangeCREndT = decltype(std::ranges::crend);
+
+static int globalBuff[8];
+
+static_assert(!std::is_invocable_v<RangeREndT, int (&&)[]>);
+static_assert(!std::is_invocable_v<RangeREndT, int (&)[]>);
+static_assert(!std::is_invocable_v<RangeREndT, int (&&)[10]>);
+static_assert( std::is_invocable_v<RangeREndT, int (&)[10]>);
+static_assert(!std::is_invocable_v<RangeCREndT, int (&&)[]>);
+static_assert(!std::is_invocable_v<RangeCREndT, int (&)[]>);
+static_assert(!std::is_invocable_v<RangeCREndT, int (&&)[10]>);
+static_assert( std::is_invocable_v<RangeCREndT, int (&)[10]>);
+
+struct Incomplete;
+static_assert(!std::is_invocable_v<RangeREndT, Incomplete(&&)[]>);
+static_assert(!std::is_invocable_v<RangeREndT, Incomplete(&&)[42]>);
+static_assert(!std::is_invocable_v<RangeCREndT, Incomplete(&&)[]>);
+static_assert(!std::is_invocable_v<RangeCREndT, Incomplete(&&)[42]>);
+
+struct REndMember {
+  int x;
+  const int* rbegin() const;
+  constexpr const int* rend() const { return &x; }
+};
+
+// Ensure that we can't call with rvalues with borrowing disabled.
+static_assert( std::is_invocable_v<RangeREndT, REndMember&>);
+static_assert(!std::is_invocable_v<RangeREndT, REndMember &&>);
+static_assert( std::is_invocable_v<RangeREndT, REndMember const&>);
+static_assert(!std::is_invocable_v<RangeREndT, REndMember const&&>);
+static_assert( std::is_invocable_v<RangeCREndT, REndMember &>);
+static_assert(!std::is_invocable_v<RangeCREndT, REndMember &&>);
+static_assert( std::is_invocable_v<RangeCREndT, REndMember const&>);
+static_assert(!std::is_invocable_v<RangeCREndT, REndMember const&&>);
+
+constexpr bool testReturnTypes() {
+  {
+    int *x[2];
+    ASSERT_SAME_TYPE(decltype(std::ranges::rend(x)), std::reverse_iterator<int**>);
+    ASSERT_SAME_TYPE(decltype(std::ranges::crend(x)), std::reverse_iterator<int* const*>);
+  }
+
+  {
+    int x[2][2];
+    ASSERT_SAME_TYPE(decltype(std::ranges::rend(x)), std::reverse_iterator<int(*)[2]>);
+    ASSERT_SAME_TYPE(decltype(std::ranges::crend(x)), std::reverse_iterator<const int(*)[2]>);
+  }
+
+  {
+    struct Different {
+      char* rbegin();
+      sentinel_wrapper<char*>& rend();
+      short* rbegin() const;
+      sentinel_wrapper<short*>& rend() const;
+    } x;
+    ASSERT_SAME_TYPE(decltype(std::ranges::rend(x)), sentinel_wrapper<char*>);
+    ASSERT_SAME_TYPE(decltype(std::ranges::crend(x)), sentinel_wrapper<short*>);
+  }
+
+  return true;
+}
+
+constexpr bool testArray() {
+  int a[2];
+  assert(std::ranges::rend(a).base() == a);
+  assert(std::ranges::crend(a).base() == a);
+
+  int b[2][2];
+  assert(std::ranges::rend(b).base() == b);
+  assert(std::ranges::crend(b).base() == b);
+
+  REndMember c[2];
+  assert(std::ranges::rend(c).base() == c);
+  assert(std::ranges::crend(c).base() == c);
+
+  return true;
+}
+
+struct REndMemberReturnsInt {
+  int rbegin() const;
+  int rend() const;
+};
+static_assert(!std::is_invocable_v<RangeREndT, REndMemberReturnsInt const&>);
+
+struct REndMemberReturnsVoidPtr {
+  const void *rbegin() const;
+  const void *rend() const;
+};
+static_assert(!std::is_invocable_v<RangeREndT, REndMemberReturnsVoidPtr const&>);
+
+struct PtrConvertible {
+  operator int*() const;
+};
+struct PtrConvertibleREndMember {
+  PtrConvertible rbegin() const;
+  PtrConvertible rend() const;
+};
+static_assert(!std::is_invocable_v<RangeREndT, PtrConvertibleREndMember const&>);
+
+struct NoRBeginMember {
+  constexpr const int* rend();
+};
+static_assert(!std::is_invocable_v<RangeREndT, NoRBeginMember const&>);
+
+struct NonConstREndMember {
+  int x;
+  constexpr int* rbegin() { return nullptr; }
+  constexpr int* rend() { return &x; }
+};
+static_assert( std::is_invocable_v<RangeREndT,  NonConstREndMember &>);
+static_assert(!std::is_invocable_v<RangeREndT,  NonConstREndMember const&>);
+static_assert(!std::is_invocable_v<RangeCREndT, NonConstREndMember &>);
+static_assert(!std::is_invocable_v<RangeCREndT, NonConstREndMember const&>);
+
+struct EnabledBorrowingREndMember {
+  constexpr int* rbegin() const { return nullptr; }
+  constexpr int* rend() const { return &globalBuff[0]; }
+};
+
+template <>
+inline constexpr bool std::ranges::enable_borrowed_range<EnabledBorrowingREndMember> = true;
+
+struct REndMemberFunction {
+  int x;
+  constexpr const int* rbegin() const { return nullptr; }
+  constexpr const int* rend() const { return &x; }
+  friend constexpr int* rend(REndMemberFunction const&);
+};
+
+struct Empty { };
+struct EmptyEndMember {
+  Empty rbegin() const;
+  Empty rend() const;
+};
+static_assert(!std::is_invocable_v<RangeREndT, EmptyEndMember const&>);
+
+struct EmptyPtrREndMember {
+  Empty x;
+  constexpr const Empty* rbegin() const { return nullptr; }
+  constexpr const Empty* rend() const { return &x; }
+};
+
+constexpr bool testREndMember() {
+  REndMember a;
+  assert(std::ranges::rend(a) == &a.x);
+  assert(std::ranges::crend(a) == &a.x);
+
+  NonConstREndMember b;
+  assert(std::ranges::rend(b) == &b.x);
+  static_assert(!std::is_invocable_v<RangeCREndT, decltype((b))>);
+
+  EnabledBorrowingREndMember c;
+  assert(std::ranges::rend(std::move(c)) == &globalBuff[0]);
+  assert(std::ranges::crend(std::move(c)) == &globalBuff[0]);
+
+  REndMemberFunction d;
+  assert(std::ranges::rend(d) == &d.x);
+  assert(std::ranges::crend(d) == &d.x);
+
+  EmptyPtrREndMember e;
+  assert(std::ranges::rend(e) == &e.x);
+  assert(std::ranges::crend(e) == &e.x);
+
+  return true;
+}
+
+struct REndFunction {
+  int x;
+  friend constexpr const int* rbegin(REndFunction const&) { return nullptr; }
+  friend constexpr const int* rend(REndFunction const& bf) { return &bf.x; }
+};
+
+static_assert( std::is_invocable_v<RangeREndT, REndFunction const&>);
+static_assert(!std::is_invocable_v<RangeREndT, REndFunction &&>);
+
+static_assert( std::is_invocable_v<RangeREndT,  REndFunction const&>);
+static_assert(!std::is_invocable_v<RangeREndT,  REndFunction &&>);
+static_assert(!std::is_invocable_v<RangeREndT,  REndFunction &>);
+static_assert( std::is_invocable_v<RangeCREndT, REndFunction const&>);
+static_assert( std::is_invocable_v<RangeCREndT, REndFunction &>);
+
+struct REndFunctionReturnsInt {
+  friend constexpr int rbegin(REndFunctionReturnsInt const&);
+  friend constexpr int rend(REndFunctionReturnsInt const&);
+};
+static_assert(!std::is_invocable_v<RangeREndT, REndFunctionReturnsInt const&>);
+
+struct REndFunctionReturnsVoidPtr {
+  friend constexpr void* rbegin(REndFunctionReturnsVoidPtr const&);
+  friend constexpr void* rend(REndFunctionReturnsVoidPtr const&);
+};
+static_assert(!std::is_invocable_v<RangeREndT, REndFunctionReturnsVoidPtr const&>);
+
+struct REndFunctionReturnsEmpty {
+  friend constexpr Empty rbegin(REndFunctionReturnsEmpty const&);
+  friend constexpr Empty rend(REndFunctionReturnsEmpty const&);
+};
+static_assert(!std::is_invocable_v<RangeREndT, REndFunctionReturnsEmpty const&>);
+
+struct REndFunctionReturnsPtrConvertible {
+  friend constexpr PtrConvertible rbegin(REndFunctionReturnsPtrConvertible const&);
+  friend constexpr PtrConvertible rend(REndFunctionReturnsPtrConvertible const&);
+};
+static_assert(!std::is_invocable_v<RangeREndT, REndFunctionReturnsPtrConvertible const&>);
+
+struct NoRBeginFunction {
+  friend constexpr const int* rend(NoRBeginFunction const&);
+};
+static_assert(!std::is_invocable_v<RangeREndT, NoRBeginFunction const&>);
+
+struct REndFunctionByValue {
+  friend constexpr int* rbegin(REndFunctionByValue) { return nullptr; }
+  friend constexpr int* rend(REndFunctionByValue) { return &globalBuff[1]; }
+};
+static_assert(!std::is_invocable_v<RangeCREndT, REndFunctionByValue>);
+
+struct REndFunctionEnabledBorrowing {
+  friend constexpr int* rbegin(REndFunctionEnabledBorrowing) { return nullptr; }
+  friend constexpr int* rend(REndFunctionEnabledBorrowing) { return &globalBuff[2]; }
+};
+template<>
+inline constexpr bool std::ranges::enable_borrowed_range<REndFunctionEnabledBorrowing> = true;
+
+struct REndFunctionReturnsEmptyPtr {
+  Empty x;
+  friend constexpr const Empty* rbegin(REndFunctionReturnsEmptyPtr const&) { return nullptr; }
+  friend constexpr const Empty* rend(REndFunctionReturnsEmptyPtr const& bf) { return &bf.x; }
+};
+
+struct REndFunctionWithDataMember {
+  int x;
+  int rend;
+  friend constexpr const int* rbegin(REndFunctionWithDataMember const&) { return nullptr; }
+  friend constexpr const int* rend(REndFunctionWithDataMember const& bf) { return &bf.x; }
+};
+
+struct REndFunctionWithPrivateEndMember : private REndMember {
+  int y;
+  friend constexpr const int* rbegin(REndFunctionWithPrivateEndMember const&) { return nullptr; }
+  friend constexpr const int* rend(REndFunctionWithPrivateEndMember const& bf) { return &bf.y; }
+};
+
+struct RBeginMemberEndFunction {
+  int x;
+  constexpr const int* rbegin() const { return nullptr; }
+  friend constexpr const int* rend(RBeginMemberEndFunction const& bf) { return &bf.x; }
+};
+
+constexpr bool testREndFunction() {
+  const REndFunction a{};
+  assert(std::ranges::rend(a) == &a.x);
+  assert(std::ranges::crend(a) == &a.x);
+  REndFunction aa{};
+  static_assert(!std::is_invocable_v<RangeREndT, decltype((aa))>);
+  assert(std::ranges::crend(aa) == &aa.x);
+
+  REndFunctionByValue b;
+  assert(std::ranges::rend(b) == &globalBuff[1]);
+  assert(std::ranges::crend(b) == &globalBuff[1]);
+
+  REndFunctionEnabledBorrowing c;
+  assert(std::ranges::rend(std::move(c)) == &globalBuff[2]);
+  assert(std::ranges::crend(std::move(c)) == &globalBuff[2]);
+
+  const REndFunctionReturnsEmptyPtr d{};
+  assert(std::ranges::rend(d) == &d.x);
+  assert(std::ranges::crend(d) == &d.x);
+  REndFunctionReturnsEmptyPtr dd{};
+  static_assert(!std::is_invocable_v<RangeREndT, decltype((dd))>);
+  assert(std::ranges::crend(dd) == &dd.x);
+
+  const REndFunctionWithDataMember e{};
+  assert(std::ranges::rend(e) == &e.x);
+  assert(std::ranges::crend(e) == &e.x);
+  REndFunctionWithDataMember ee{};
+  static_assert(!std::is_invocable_v<RangeREndT, decltype((ee))>);
+  assert(std::ranges::crend(ee) == &ee.x);
+
+  const REndFunctionWithPrivateEndMember f{};
+  assert(std::ranges::rend(f) == &f.y);
+  assert(std::ranges::crend(f) == &f.y);
+  REndFunctionWithPrivateEndMember ff{};
+  static_assert(!std::is_invocable_v<RangeREndT, decltype((ff))>);
+  assert(std::ranges::crend(ff) == &ff.y);
+
+  const RBeginMemberEndFunction g{};
+  assert(std::ranges::rend(g) == &g.x);
+  assert(std::ranges::crend(g) == &g.x);
+  RBeginMemberEndFunction gg{};
+  static_assert(!std::is_invocable_v<RangeREndT, decltype((gg))>);
+  assert(std::ranges::crend(gg) == &gg.x);
+
+  return true;
+}
+
+
+struct MemberBeginEnd {
+  int b, e;
+  char cb, ce;
+  constexpr bidirectional_iterator<int*> begin() { return bidirectional_iterator<int*>(&b); }
+  constexpr bidirectional_iterator<int*> end() { return bidirectional_iterator<int*>(&e); }
+  constexpr bidirectional_iterator<const char*> begin() const { return bidirectional_iterator<const char*>(&cb); }
+  constexpr bidirectional_iterator<const char*> end() const { return bidirectional_iterator<const char*>(&ce); }
+};
+static_assert( std::is_invocable_v<RangeREndT, MemberBeginEnd&>);
+static_assert( std::is_invocable_v<RangeREndT, MemberBeginEnd const&>);
+static_assert( std::is_invocable_v<RangeCREndT, MemberBeginEnd const&>);
+
+struct FunctionBeginEnd {
+  int b, e;
+  char cb, ce;
+  friend constexpr bidirectional_iterator<int*> begin(FunctionBeginEnd& v) {
+    return bidirectional_iterator<int*>(&v.b);
+  }
+  friend constexpr bidirectional_iterator<int*> end(FunctionBeginEnd& v) { return bidirectional_iterator<int*>(&v.e); }
+  friend constexpr bidirectional_iterator<const char*> begin(const FunctionBeginEnd& v) {
+    return bidirectional_iterator<const char*>(&v.cb);
+  }
+  friend constexpr bidirectional_iterator<const char*> end(const FunctionBeginEnd& v) {
+    return bidirectional_iterator<const char*>(&v.ce);
+  }
+};
+static_assert( std::is_invocable_v<RangeREndT, FunctionBeginEnd&>);
+static_assert( std::is_invocable_v<RangeREndT, FunctionBeginEnd const&>);
+static_assert( std::is_invocable_v<RangeCREndT, FunctionBeginEnd const&>);
+
+struct MemberBeginFunctionEnd {
+  int b, e;
+  char cb, ce;
+  constexpr bidirectional_iterator<int*> begin() { return bidirectional_iterator<int*>(&b); }
+  friend constexpr bidirectional_iterator<int*> end(MemberBeginFunctionEnd& v) {
+    return bidirectional_iterator<int*>(&v.e);
+  }
+  constexpr bidirectional_iterator<const char*> begin() const { return bidirectional_iterator<const char*>(&cb); }
+  friend constexpr bidirectional_iterator<const char*> end(const MemberBeginFunctionEnd& v) {
+    return bidirectional_iterator<const char*>(&v.ce);
+  }
+};
+static_assert( std::is_invocable_v<RangeREndT, MemberBeginFunctionEnd&>);
+static_assert( std::is_invocable_v<RangeREndT, MemberBeginFunctionEnd const&>);
+static_assert( std::is_invocable_v<RangeCREndT, MemberBeginFunctionEnd const&>);
+
+struct FunctionBeginMemberEnd {
+  int b, e;
+  char cb, ce;
+  friend constexpr bidirectional_iterator<int*> begin(FunctionBeginMemberEnd& v) {
+    return bidirectional_iterator<int*>(&v.b);
+  }
+  constexpr bidirectional_iterator<int*> end() { return bidirectional_iterator<int*>(&e); }
+  friend constexpr bidirectional_iterator<const char*> begin(const FunctionBeginMemberEnd& v) {
+    return bidirectional_iterator<const char*>(&v.cb);
+  }
+  constexpr bidirectional_iterator<const char*> end() const { return bidirectional_iterator<const char*>(&ce); }
+};
+static_assert( std::is_invocable_v<RangeREndT, FunctionBeginMemberEnd&>);
+static_assert( std::is_invocable_v<RangeREndT, FunctionBeginMemberEnd const&>);
+static_assert( std::is_invocable_v<RangeCREndT, FunctionBeginMemberEnd const&>);
+
+struct MemberBeginEndDifferentTypes {
+  bidirectional_iterator<int*> begin();
+  bidirectional_iterator<const int*> end();
+};
+static_assert(!std::is_invocable_v<RangeREndT, MemberBeginEndDifferentTypes&>);
+static_assert(!std::is_invocable_v<RangeCREndT, MemberBeginEndDifferentTypes&>);
+
+struct FunctionBeginEndDifferentTypes {
+  friend bidirectional_iterator<int*> begin(FunctionBeginEndDifferentTypes&);
+  friend bidirectional_iterator<const int*> end(FunctionBeginEndDifferentTypes&);
+};
+static_assert(!std::is_invocable_v<RangeREndT, FunctionBeginEndDifferentTypes&>);
+static_assert(!std::is_invocable_v<RangeCREndT, FunctionBeginEndDifferentTypes&>);
+
+struct MemberBeginEndForwardIterators {
+  forward_iterator<int*> begin();
+  forward_iterator<int*> end();
+};
+static_assert(!std::is_invocable_v<RangeREndT, MemberBeginEndForwardIterators&>);
+static_assert(!std::is_invocable_v<RangeCREndT, MemberBeginEndForwardIterators&>);
+
+struct FunctionBeginEndForwardIterators {
+  friend forward_iterator<int*> begin(FunctionBeginEndForwardIterators&);
+  friend forward_iterator<int*> end(FunctionBeginEndForwardIterators&);
+};
+static_assert(!std::is_invocable_v<RangeREndT, FunctionBeginEndForwardIterators&>);
+static_assert(!std::is_invocable_v<RangeCREndT, FunctionBeginEndForwardIterators&>);
+
+struct MemberBeginOnly {
+  bidirectional_iterator<int*> begin() const;
+};
+static_assert(!std::is_invocable_v<RangeREndT, MemberBeginOnly&>);
+static_assert(!std::is_invocable_v<RangeCREndT, MemberBeginOnly&>);
+
+struct FunctionBeginOnly {
+  friend bidirectional_iterator<int*> begin(FunctionBeginOnly&);
+};
+static_assert(!std::is_invocable_v<RangeREndT, FunctionBeginOnly&>);
+static_assert(!std::is_invocable_v<RangeCREndT, FunctionBeginOnly&>);
+
+struct MemberEndOnly {
+  bidirectional_iterator<int*> end() const;
+};
+static_assert(!std::is_invocable_v<RangeREndT, MemberEndOnly&>);
+static_assert(!std::is_invocable_v<RangeCREndT, MemberEndOnly&>);
+
+struct FunctionEndOnly {
+  friend bidirectional_iterator<int*> end(FunctionEndOnly&);
+};
+static_assert(!std::is_invocable_v<RangeREndT, FunctionEndOnly&>);
+static_assert(!std::is_invocable_v<RangeCREndT, FunctionEndOnly&>);
+
+// Make sure there is no clash between the following cases:
+// - the case that handles classes defining member `rbegin` and `rend` functions;
+// - the case that handles classes defining `begin` and `end` functions returning reversible iterators.
+struct MemberBeginAndRBegin {
+  int* begin() const;
+  int* end() const;
+  int* rbegin() const;
+  int* rend() const;
+};
+static_assert( std::is_invocable_v<RangeREndT, MemberBeginAndRBegin&>);
+static_assert( std::is_invocable_v<RangeCREndT, MemberBeginAndRBegin&>);
+static_assert( std::same_as<std::invoke_result_t<RangeREndT, MemberBeginAndRBegin&>, int*>);
+static_assert( std::same_as<std::invoke_result_t<RangeCREndT, MemberBeginAndRBegin&>, int*>);
+
+constexpr bool testBeginEnd() {
+  MemberBeginEnd a{};
+  const MemberBeginEnd aa{};
+  assert(std::ranges::rend(a).base().base() == &a.b);
+  assert(std::ranges::crend(a).base().base() == &a.cb);
+  assert(std::ranges::rend(aa).base().base() == &aa.cb);
+  assert(std::ranges::crend(aa).base().base() == &aa.cb);
+
+  FunctionBeginEnd b{};
+  const FunctionBeginEnd bb{};
+  assert(std::ranges::rend(b).base().base() == &b.b);
+  assert(std::ranges::crend(b).base().base() == &b.cb);
+  assert(std::ranges::rend(bb).base().base() == &bb.cb);
+  assert(std::ranges::crend(bb).base().base() == &bb.cb);
+
+  MemberBeginFunctionEnd c{};
+  const MemberBeginFunctionEnd cc{};
+  assert(std::ranges::rend(c).base().base() == &c.b);
+  assert(std::ranges::crend(c).base().base() == &c.cb);
+  assert(std::ranges::rend(cc).base().base() == &cc.cb);
+  assert(std::ranges::crend(cc).base().base() == &cc.cb);
+
+  FunctionBeginMemberEnd d{};
+  const FunctionBeginMemberEnd dd{};
+  assert(std::ranges::rend(d).base().base() == &d.b);
+  assert(std::ranges::crend(d).base().base() == &d.cb);
+  assert(std::ranges::rend(dd).base().base() == &dd.cb);
+  assert(std::ranges::crend(dd).base().base() == &dd.cb);
+
+  return true;
+}
+
+
+ASSERT_NOEXCEPT(std::ranges::rend(std::declval<int (&)[10]>()));
+ASSERT_NOEXCEPT(std::ranges::crend(std::declval<int (&)[10]>()));
+
+struct NoThrowMemberREnd {
+  ThrowingIterator<int> rbegin() const;
+  ThrowingIterator<int> rend() const noexcept; // auto(t.rend()) doesn't throw
+} ntmre;
+static_assert(noexcept(std::ranges::rend(ntmre)));
+static_assert(noexcept(std::ranges::crend(ntmre)));
+
+struct NoThrowADLREnd {
+  ThrowingIterator<int> rbegin() const;
+  friend ThrowingIterator<int> rend(NoThrowADLREnd&) noexcept;  // auto(rend(t)) doesn't throw
+  friend ThrowingIterator<int> rend(const NoThrowADLREnd&) noexcept;
+} ntare;
+static_assert(noexcept(std::ranges::rend(ntare)));
+static_assert(noexcept(std::ranges::crend(ntare)));
+
+struct NoThrowMemberREndReturnsRef {
+  ThrowingIterator<int> rbegin() const;
+  ThrowingIterator<int>& rend() const noexcept; // auto(t.rend()) may throw
+} ntmrerr;
+static_assert(!noexcept(std::ranges::rend(ntmrerr)));
+static_assert(!noexcept(std::ranges::crend(ntmrerr)));
+
+struct REndReturnsArrayRef {
+    auto rbegin() const noexcept -> int(&)[10];
+    auto rend() const noexcept -> int(&)[10];
+} rerar;
+static_assert(noexcept(std::ranges::rend(rerar)));
+static_assert(noexcept(std::ranges::crend(rerar)));
+
+struct NoThrowBeginThrowingEnd {
+  int* begin() const noexcept;
+  int* end() const;
+} ntbte;
+static_assert(noexcept(std::ranges::rend(ntbte)));
+static_assert(noexcept(std::ranges::crend(ntbte)));
+
+struct NoThrowEndThrowingBegin {
+  int* begin() const;
+  int* end() const noexcept;
+} ntetb;
+static_assert(!noexcept(std::ranges::rend(ntetb)));
+static_assert(!noexcept(std::ranges::crend(ntetb)));
+
+// Test ADL-proofing.
+struct Incomplete;
+template<class T> struct Holder { T t; };
+static_assert(!std::is_invocable_v<RangeREndT, Holder<Incomplete>*>);
+static_assert(!std::is_invocable_v<RangeREndT, Holder<Incomplete>*&>);
+static_assert(!std::is_invocable_v<RangeCREndT, Holder<Incomplete>*>);
+static_assert(!std::is_invocable_v<RangeCREndT, Holder<Incomplete>*&>);
+
+int main(int, char**) {
+  static_assert(testReturnTypes());
+
+  testArray();
+  static_assert(testArray());
+
+  testREndMember();
+  static_assert(testREndMember());
+
+  testREndFunction();
+  static_assert(testREndFunction());
+
+  testBeginEnd();
+  static_assert(testBeginEnd());
+
+  return 0;
+}


        


More information about the libcxx-commits mailing list