diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -348,6 +348,8 @@
     --------------------------------------------------- -----------------
     ``__cpp_lib_ranges_chunk_by``                       *unimplemented*
     --------------------------------------------------- -----------------
+    ``__cpp_lib_ranges_enumerate``                      ``202302L``
+    --------------------------------------------------- -----------------
     ``__cpp_lib_ranges_iota``                           *unimplemented*
     --------------------------------------------------- -----------------
     ``__cpp_lib_ranges_join_with``                      *unimplemented*
diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -105,7 +105,7 @@
 "","","","","","",""
 "`P0290R4 <https://wg21.link/P0290R4>`__","LWG", "``apply()`` for ``synchronized_value<T>``","February 2023","","","|concurrency TS|"
 "`P2770R0 <https://wg21.link/P2770R0>`__","LWG", "Stashing stashing ``iterators`` for proper flattening","February 2023","","","|ranges|"
-"`P2164R9 <https://wg21.link/P2164R9>`__","LWG", "``views::enumerate``","February 2023","","","|ranges|"
+"`P2164R9 <https://wg21.link/P2164R9>`__","LWG", "``views::enumerate``","February 2023","|Complete|","18.0","|ranges|"
 "`P2711R1 <https://wg21.link/P2711R1>`__","LWG", "Making multi-param constructors of ``views`` ``explicit``","February 2023","|Partial| [#note-P2711R1]_","","|ranges|"
 "`P2609R3 <https://wg21.link/P2609R3>`__","LWG", "Relaxing Ranges Just A Smidge","February 2023","","","|ranges|"
 "`P2713R1 <https://wg21.link/P2713R1>`__","LWG", "Escaping improvements in ``std::format``","February 2023","","","|format|"
diff --git a/libcxx/docs/Status/Cxx2cIssues.csv b/libcxx/docs/Status/Cxx2cIssues.csv
--- a/libcxx/docs/Status/Cxx2cIssues.csv
+++ b/libcxx/docs/Status/Cxx2cIssues.csv
@@ -8,8 +8,8 @@
 "`3903 <https://wg21.link/LWG3903>`__","span destructor is redundantly noexcept","Varna June 2023","|Complete|","7.0",""
 "`3904 <https://wg21.link/LWG3904>`__","``lazy_split_view::outer-iterator``'s const-converting constructor isn't setting ``trailing_empty_``","Varna June 2023","","","|ranges|"
 "`3905 <https://wg21.link/LWG3905>`__","Type of ``std::fexcept_t``","Varna June 2023","|Complete|","3.4",""
-"`3912 <https://wg21.link/LWG3912>`__","``enumerate_view::iterator::operator-`` should be ``noexcept``","Varna June 2023","","","|ranges|"
-"`3914 <https://wg21.link/LWG3914>`__","Inconsistent template-head of ``ranges::enumerate_view``","Varna June 2023","","","|ranges|"
+"`3912 <https://wg21.link/LWG3912>`__","``enumerate_view::iterator::operator-`` should be ``noexcept``","Varna June 2023","|Complete|","18.0","|ranges|"
+"`3914 <https://wg21.link/LWG3914>`__","Inconsistent template-head of ``ranges::enumerate_view``","Varna June 2023","|Nothing To Do|","","|ranges|"
 "`3915 <https://wg21.link/LWG3915>`__","Redundant paragraph about expression variations","Varna June 2023","","","|ranges|"
 "`3925 <https://wg21.link/LWG3925>`__","Concept ``formattable``'s definition is incorrect","Varna June 2023","|Complete|","17.0","|format|"
 "`3927 <https://wg21.link/LWG3927>`__","Unclear preconditions for ``operator[]`` for sequence containers","Varna June 2023","|Nothing To Do|","",""
diff --git a/libcxx/docs/Status/RangesViews.csv b/libcxx/docs/Status/RangesViews.csv
--- a/libcxx/docs/Status/RangesViews.csv
+++ b/libcxx/docs/Status/RangesViews.csv
@@ -35,4 +35,4 @@
 C++23,`as_const <https://wg21.link/P2278R4>`_,Unassigned,No patch yet,Not started
 C++23,`as_rvalue <https://wg21.link/P2446R2>`_,Nikolas Klauser,`D137637 <https://llvm.org/D137637>`_,✅
 C++23,`stride <https://wg21.link/P1899R3>`_,Hristo Hristov,No patch yet,In Progress
-C++23,`enumerate <https://wg21.link/P2164R9>`_,Hristo Hristov,No patch yet,In Progress
+C++23,`enumerate <https://wg21.link/P2164R9>`_,Hristo Hristov,No patch yet,✅
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -614,6 +614,7 @@
   __ranges/empty_view.h
   __ranges/enable_borrowed_range.h
   __ranges/enable_view.h
+  __ranges/enumerate_view.h
   __ranges/filter_view.h
   __ranges/from_range.h
   __ranges/iota_view.h
diff --git a/libcxx/include/__ranges/concepts.h b/libcxx/include/__ranges/concepts.h
--- a/libcxx/include/__ranges/concepts.h
+++ b/libcxx/include/__ranges/concepts.h
@@ -141,6 +141,17 @@
       (is_lvalue_reference_v<_Tp> ||
        (movable<remove_reference_t<_Tp>> && !__is_std_initializer_list<remove_cvref_t<_Tp>>))));
 
+#  if _LIBCPP_STD_VER >= 23
+
+  // [concept.object]
+
+  template <class _Rp>
+  concept __range_with_movable_references =
+      ranges::input_range<_Rp> && std::move_constructible<ranges::range_reference_t<_Rp>> &&
+      std::move_constructible<ranges::range_rvalue_reference_t<_Rp>>; // exposition only
+
+#  endif // _LIBCPP_STD_VER >= 23
+
 } // namespace ranges
 
 #endif // _LIBCPP_STD_VER >= 20
diff --git a/libcxx/include/__ranges/enumerate_view.h b/libcxx/include/__ranges/enumerate_view.h
new file mode 100644
--- /dev/null
+++ b/libcxx/include/__ranges/enumerate_view.h
@@ -0,0 +1,331 @@
+// -*- 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_ENUMERATE_VIEW_H
+#define _LIBCPP___RANGES_ENUMERATE_VIEW_H
+
+#include <__concepts/convertible_to.h>
+#include <__config>
+#include <__iterator/concepts.h>
+#include <__iterator/distance.h>
+#include <__iterator/iter_move.h>
+#include <__iterator/iterator_traits.h>
+#include <__ranges/access.h>
+#include <__ranges/all.h>
+#include <__ranges/concepts.h>
+#include <__ranges/enable_borrowed_range.h>
+#include <__ranges/range_adaptor.h>
+#include <__ranges/size.h>
+#include <__ranges/view_interface.h>
+#include <__type_traits/maybe_const.h>
+#include <__utility/forward.h>
+#include <__utility/move.h>
+#include <tuple>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 23
+
+namespace ranges {
+
+// [range.enumerate.view]
+
+template <view _View>
+  requires __range_with_movable_references<_View>
+class enumerate_view : public view_interface<enumerate_view<_View>> {
+  _View __base_ = _View(); // exposition only
+
+  // [range.enumerate.iterator], class template enumerate_view::iterator
+  template <bool _Const>
+  class __iterator; // exposition only
+
+  // [range.enumerate.sentinel], class template enumerate_view::sentinel
+  template <bool _Const>
+  class __sentinel; // exposition only
+
+public:
+  template <bool _AnyConst>
+  _LIBCPP_HIDE_FROM_ABI static constexpr decltype(auto) __get_current(const __iterator<_AnyConst>& __iter) {
+    return (__iter.__current_);
+  }
+
+public:
+  _LIBCPP_HIDE_FROM_ABI constexpr enumerate_view()
+    requires default_initializable<_View>
+  = default;
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit enumerate_view(_View __base) : __base_(std::move(__base)){};
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto begin()
+    requires(!__simple_view<_View>)
+  {
+    return __iterator<false>(ranges::begin(__base_), 0);
+  }
+  _LIBCPP_HIDE_FROM_ABI constexpr auto begin() const
+    requires __range_with_movable_references<const _View>
+  {
+    return __iterator<true>(ranges::begin(__base_), 0);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto end()
+    requires(!__simple_view<_View>)
+  {
+    if constexpr (common_range<_View> && sized_range<_View>)
+      return __iterator<false>(ranges::end(__base_), ranges::distance(__base_));
+    else
+      return __sentinel<false>(ranges::end(__base_));
+  }
+  _LIBCPP_HIDE_FROM_ABI constexpr auto end() const
+    requires __range_with_movable_references<const _View>
+  {
+    if constexpr (common_range<const _View> && sized_range<const _View>)
+      return __iterator<true>(ranges::end(__base_), ranges::distance(__base_));
+    else
+      return __sentinel<true>(ranges::end(__base_));
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto size()
+    requires sized_range<_View>
+  {
+    return ranges::size(__base_);
+  }
+  _LIBCPP_HIDE_FROM_ABI constexpr auto size() const
+    requires sized_range<const _View>
+  {
+    return ranges::size(__base_);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr _View base() const&
+    requires copy_constructible<_View>
+  {
+    return __base_;
+  }
+  _LIBCPP_HIDE_FROM_ABI constexpr _View base() && { return std::move(__base_); }
+};
+
+template <class _Range>
+enumerate_view(_Range&&) -> enumerate_view<views::all_t<_Range>>;
+
+// [range.enumerate.iterator]
+
+template <view _View>
+  requires __range_with_movable_references<_View>
+template <bool _Const>
+class enumerate_view<_View>::__iterator {
+  using _Base = __maybe_const<_Const, _View>; // exposition only
+
+  static consteval auto __get_iterator_concept() {
+    if constexpr (random_access_range<_Base>) {
+      return random_access_iterator_tag{};
+    } else if constexpr (bidirectional_range<_Base>) {
+      return bidirectional_iterator_tag{};
+    } else if constexpr (forward_range<_Base>) {
+      return forward_iterator_tag{};
+    } else {
+      return input_iterator_tag{};
+    }
+  }
+
+  friend class enumerate_view<_View>;
+
+public:
+  using iterator_category = input_iterator_tag;
+  using iterator_concept  = decltype(__get_iterator_concept());
+  using difference_type   = range_difference_t<_Base>;
+  using value_type        = tuple<difference_type, range_value_t<_Base>>;
+
+private:
+  using __reference_type       = tuple<difference_type, range_reference_t<_Base>>; // exposition only
+  iterator_t<_Base> __current_ = iterator_t<_Base>();                              // exposition only
+  difference_type __pos_       = 0;                                                // exposition only
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit __iterator(iterator_t<_Base> __current, difference_type __pos)
+      : __current_(std::move(__current)), __pos_(std::move(__pos)) {} // exposition only
+
+public:
+  _LIBCPP_HIDE_FROM_ABI __iterator()
+    requires default_initializable<iterator_t<_Base>>
+  = default;
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator(__iterator<!_Const> __i)
+    requires _Const && convertible_to<iterator_t<_View>, iterator_t<_Base>>
+      : __current_(std::move(__i.__current)), __pos_(std::move(__i.__pos)){};
+
+  _LIBCPP_HIDE_FROM_ABI constexpr const iterator_t<_Base>& base() const& noexcept { return __current_; }
+  _LIBCPP_HIDE_FROM_ABI constexpr iterator_t<_Base> base() && { return std::move(__current_); }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr difference_type index() const noexcept { return __pos_; }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator*() const { return __reference_type(__pos_, *__current_); }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() {
+    ++__current_;
+    ++__pos_;
+    return *this;
+  }
+  _LIBCPP_HIDE_FROM_ABI constexpr void operator++(int) { return ++*this; }
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator++(int)
+    requires forward_range<_Base>
+  {
+    auto __temp = *this;
+    ++*this;
+    return __temp;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator--()
+    requires bidirectional_range<_Base>
+  {
+    --__current_;
+    --__pos_;
+    return *this;
+  }
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator--(int)
+    requires bidirectional_range<_Base>
+  {
+    auto __temp = *this;
+    --*this;
+    return *__temp;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator+=(difference_type __n)
+    requires random_access_range<_Base>
+  {
+    __current_ += __n;
+    __pos_ += __n;
+    return *this;
+  }
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator-=(difference_type __n)
+    requires random_access_range<_Base>
+  {
+    __current_ -= __n;
+    __pos_ -= __n;
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto operator[](difference_type __n) const
+    requires random_access_range<_Base>
+  {
+    return __reference_type(__pos_ + __n, __current_[__n]);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __iterator& __y) noexcept {
+    return __x.__pos_ == __y.__pos_;
+  }
+  _LIBCPP_HIDE_FROM_ABI friend constexpr strong_ordering
+  operator<=>(const __iterator& __x, const __iterator& __y) noexcept {
+    return __x.__pos_ <=> __y.__pos_;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator+(const __iterator& __i, difference_type __n)
+    requires random_access_range<_Base>
+  {
+    auto __temp = __i;
+    __temp += __n;
+    return __temp;
+  }
+  _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator+(difference_type __n, const __iterator& __i)
+    requires random_access_range<_Base>
+  {
+    return __i + __n;
+  }
+  _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator operator-(const __iterator& __i, difference_type __n)
+    requires random_access_range<_Base>
+  {
+    auto __temp = __i;
+    __temp -= __n;
+    return __temp;
+  }
+  _LIBCPP_HIDE_FROM_ABI friend constexpr difference_type
+  operator-(const __iterator& __x, const __iterator& __y) noexcept {
+    return __x.__pos_ - __y.__pos_;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto iter_move(const __iterator& __i) noexcept(
+      noexcept(ranges::iter_move(__i.__current_)) && is_nothrow_move_constructible_v<range_rvalue_reference_t<_Base>>) {
+    return tuple<difference_type, range_rvalue_reference_t<_Base>>(__i.__pos_, ranges::iter_move(__i.__current_));
+  }
+};
+
+// [range.enumerate.sentinel]
+
+template <view _View>
+  requires __range_with_movable_references<_View>
+template <bool _Const>
+class enumerate_view<_View>::__sentinel {
+  using _Base              = __maybe_const<_Const, _View>; // exposition only
+  sentinel_t<_Base> __end_ = sentinel_t<_Base>();
+  _LIBCPP_HIDE_FROM_ABI // exposition only
+      constexpr explicit __sentinel(sentinel_t<_Base> __end)
+      : __end_(std::move(__end)) {} // exposition only
+
+  friend class enumerate_view<_View>;
+
+public:
+  _LIBCPP_HIDE_FROM_ABI __sentinel() = default;
+  _LIBCPP_HIDE_FROM_ABI constexpr __sentinel(__sentinel<!_Const> __other)
+    requires _Const && convertible_to<sentinel_t<_View>, sentinel_t<_Base>>
+      : __end_(std::move(__other.__end_)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr sentinel_t<_Base> base() const { return __end_; }
+
+  template <bool _OtherConst>
+    requires sentinel_for<sentinel_t<_Base>, iterator_t<__maybe_const<_OtherConst, _View>>>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator<_OtherConst>& __x, const __sentinel& __y) {
+    return enumerate_view<_View>::__get_current(__x) == __y.__end_;
+  }
+
+  template <bool _OtherConst>
+    requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<__maybe_const<_OtherConst, _View>>>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr range_difference_t<__maybe_const<_OtherConst, _View>>
+  operator-(const __iterator<_OtherConst>& __x, const __sentinel& __y) {
+    return enumerate_view<_View>::__get_current(__x) - __y.__end_;
+  }
+
+  template <bool _OtherConst>
+    requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<__maybe_const<_OtherConst, _View>>>
+  _LIBCPP_HIDE_FROM_ABI friend constexpr range_difference_t<__maybe_const<_OtherConst, _View>>
+  operator-(const __sentinel& __x, const __iterator<_OtherConst>& __y) {
+    return __x.__end_ - enumerate_view<_View>::__get_current(__y);
+  }
+};
+
+template <class _View>
+constexpr bool enable_borrowed_range<enumerate_view<_View>> = enable_borrowed_range<_View>; // freestanding
+
+namespace views {
+namespace __enumerate {
+
+// clang-format off
+struct __fn : __range_adaptor_closure<__fn> {
+  template <class _Range>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Range&& __range) const
+      noexcept(noexcept(enumerate_view(std::forward<_Range>(__range))))
+          ->   decltype(enumerate_view(std::forward<_Range>(__range))) {
+    return              enumerate_view(std::forward<_Range>(__range));
+  }
+};
+// clang-format on
+
+} // namespace __enumerate
+
+inline namespace __cpo {
+
+inline constexpr auto enumerate = __enumerate::__fn{};
+
+} // namespace __cpo
+} // namespace views
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___RANGES_ENUMERATE_VIEW_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1677,6 +1677,7 @@
 module std_private_ranges_empty_view                 [system] { header "__ranges/empty_view.h" }
 module std_private_ranges_enable_borrowed_range      [system] { header "__ranges/enable_borrowed_range.h" }
 module std_private_ranges_enable_view                [system] { header "__ranges/enable_view.h" }
+module std_private_ranges_enumerate_view             [system] { header "__ranges/enumerate_view.h" }
 module std_private_ranges_filter_view                [system] {
   header "__ranges/filter_view.h"
   export std_private_ranges_range_adaptor
diff --git a/libcxx/include/ranges b/libcxx/include/ranges
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -314,6 +314,17 @@
 
   namespace views { template<class T> inline constexpr unspecified istream = unspecified; }
 
+  // [range.enumerate], enumerate view
+  template<view View>
+    requires see below
+  class enumerate_view;                                                             // freestanding, since C++23
+
+  template<class View>
+    constexpr bool enable_borrowed_range<enumerate_view<View>> =                    // freestanding, since C++23
+      enable_borrowed_range<View>;
+
+  namespace views { inline constexpr unspecified enumerate = unspecified; }         // freestanding, since C++23
+
   // [range.zip], zip view
   template<input_range... Views>
     requires (view<Views> && ...) && (sizeof...(Views) > 0)
@@ -385,6 +396,7 @@
 #include <__ranges/empty_view.h>
 #include <__ranges/enable_borrowed_range.h>
 #include <__ranges/enable_view.h>
+#include <__ranges/enumerate_view.h>
 #include <__ranges/filter_view.h>
 #include <__ranges/from_range.h>
 #include <__ranges/iota_view.h>
diff --git a/libcxx/include/version b/libcxx/include/version
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -156,6 +156,7 @@
 __cpp_lib_ranges_as_rvalue                              202207L <ranges>
 __cpp_lib_ranges_chunk                                  202202L <ranges>
 __cpp_lib_ranges_chunk_by                               202202L <ranges>
+__cpp_lib_ranges_enumerate                              202302L <ranges>
 __cpp_lib_ranges_iota                                   202202L <numeric>
 __cpp_lib_ranges_join_with                              202202L <ranges>
 __cpp_lib_ranges_repeat                                 202207L <ranges>
@@ -435,6 +436,7 @@
 # define __cpp_lib_ranges_as_rvalue                     202207L
 // # define __cpp_lib_ranges_chunk                         202202L
 // # define __cpp_lib_ranges_chunk_by                      202202L
+# define __cpp_lib_ranges_enumerate                     202302L
 // # define __cpp_lib_ranges_iota                          202202L
 // # define __cpp_lib_ranges_join_with                     202202L
 # define __cpp_lib_ranges_repeat                        202207L
diff --git a/libcxx/modules/std/ranges.inc b/libcxx/modules/std/ranges.inc
--- a/libcxx/modules/std/ranges.inc
+++ b/libcxx/modules/std/ranges.inc
@@ -322,6 +322,13 @@
       using std::ranges::views::cartesian_product;
     }
 #endif
+
+    // [range.enumerate]
+    using std::ranges::enumerate_view;
+
+    namespace views {
+      using std::ranges::views::enumerate;
+    }
   } // namespace ranges
 
   namespace views = ranges::views;
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
@@ -20,6 +20,7 @@
     __cpp_lib_ranges_as_rvalue       202207L [C++23]
     __cpp_lib_ranges_chunk           202202L [C++23]
     __cpp_lib_ranges_chunk_by        202202L [C++23]
+    __cpp_lib_ranges_enumerate       202302L [C++23]
     __cpp_lib_ranges_join_with       202202L [C++23]
     __cpp_lib_ranges_repeat          202207L [C++23]
     __cpp_lib_ranges_slide           202202L [C++23]
@@ -48,6 +49,10 @@
 #   error "__cpp_lib_ranges_chunk_by should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_join_with
 #   error "__cpp_lib_ranges_join_with should not be defined before c++23"
 # endif
@@ -86,6 +91,10 @@
 #   error "__cpp_lib_ranges_chunk_by should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_join_with
 #   error "__cpp_lib_ranges_join_with should not be defined before c++23"
 # endif
@@ -124,6 +133,10 @@
 #   error "__cpp_lib_ranges_chunk_by should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_join_with
 #   error "__cpp_lib_ranges_join_with should not be defined before c++23"
 # endif
@@ -165,6 +178,10 @@
 #   error "__cpp_lib_ranges_chunk_by should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_join_with
 #   error "__cpp_lib_ranges_join_with should not be defined before c++23"
 # endif
@@ -227,6 +244,13 @@
 #   endif
 # endif
 
+# ifndef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_enumerate != 202302L
+#   error "__cpp_lib_ranges_enumerate should have the value 202302L in c++23"
+# endif
+
 # if !defined(_LIBCPP_VERSION)
 #   ifndef __cpp_lib_ranges_join_with
 #     error "__cpp_lib_ranges_join_with should be defined in c++23"
@@ -322,6 +346,13 @@
 #   endif
 # endif
 
+# ifndef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_enumerate != 202302L
+#   error "__cpp_lib_ranges_enumerate should have the value 202302L in c++26"
+# endif
+
 # if !defined(_LIBCPP_VERSION)
 #   ifndef __cpp_lib_ranges_join_with
 #     error "__cpp_lib_ranges_join_with should be defined in c++26"
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -144,6 +144,7 @@
     __cpp_lib_ranges_as_rvalue                       202207L [C++23]
     __cpp_lib_ranges_chunk                           202202L [C++23]
     __cpp_lib_ranges_chunk_by                        202202L [C++23]
+    __cpp_lib_ranges_enumerate                       202302L [C++23]
     __cpp_lib_ranges_iota                            202202L [C++23]
     __cpp_lib_ranges_join_with                       202202L [C++23]
     __cpp_lib_ranges_repeat                          202207L [C++23]
@@ -700,6 +701,10 @@
 #   error "__cpp_lib_ranges_chunk_by should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_iota
 #   error "__cpp_lib_ranges_iota should not be defined before c++23"
 # endif
@@ -1458,6 +1463,10 @@
 #   error "__cpp_lib_ranges_chunk_by should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_iota
 #   error "__cpp_lib_ranges_iota should not be defined before c++23"
 # endif
@@ -2387,6 +2396,10 @@
 #   error "__cpp_lib_ranges_chunk_by should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_iota
 #   error "__cpp_lib_ranges_iota should not be defined before c++23"
 # endif
@@ -3586,6 +3599,10 @@
 #   error "__cpp_lib_ranges_chunk_by should not be defined before c++23"
 # endif
 
+# ifdef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should not be defined before c++23"
+# endif
+
 # ifdef __cpp_lib_ranges_iota
 #   error "__cpp_lib_ranges_iota should not be defined before c++23"
 # endif
@@ -4983,6 +5000,13 @@
 #   endif
 # endif
 
+# ifndef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_enumerate != 202302L
+#   error "__cpp_lib_ranges_enumerate should have the value 202302L in c++23"
+# endif
+
 # if !defined(_LIBCPP_VERSION)
 #   ifndef __cpp_lib_ranges_iota
 #     error "__cpp_lib_ranges_iota should be defined in c++23"
@@ -6536,6 +6560,13 @@
 #   endif
 # endif
 
+# ifndef __cpp_lib_ranges_enumerate
+#   error "__cpp_lib_ranges_enumerate should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_enumerate != 202302L
+#   error "__cpp_lib_ranges_enumerate should have the value 202302L in c++26"
+# endif
+
 # if !defined(_LIBCPP_VERSION)
 #   ifndef __cpp_lib_ranges_iota
 #     error "__cpp_lib_ranges_iota should be defined in c++26"
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/adaptor.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/adaptor.pass.cpp
@@ -0,0 +1,102 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// std::views::enumerate
+
+#include <cassert>
+#include <ranges>
+#include <string_view>
+
+#include "types.h"
+
+// Concepts
+
+template <class View, class T>
+concept CanBePiped = requires(View&& view, T&& t) {
+  { std::forward<View>(view) | std::forward<T>(t) };
+};
+
+// Helpers
+
+template <typename View, typename T = int>
+constexpr void
+compareViews(View v,
+             std::initializer_list<std::tuple<typename std::ranges::iterator_t<View>::difference_type, T>> list) {
+  auto b1 = v.begin();
+  auto e1 = v.end();
+  auto b2 = list.begin();
+  auto e2 = list.end();
+  for (; b1 != e1 && b2 != e2; ++b1, ++b2) {
+    assert(*b1 == *b2);
+  }
+  assert(b1 == e1);
+  assert(b2 == e2);
+}
+
+// Test SFINAE friendliness
+
+static_assert(CanBePiped<Range, decltype(std::views::enumerate)>);
+
+static_assert(!std::is_invocable_v<decltype(std::views::enumerate)>);
+static_assert(std::is_invocable_v<decltype(std::views::enumerate), Range>);
+static_assert(!std::is_invocable_v<decltype(std::views::enumerate), NotAView>);
+static_assert(!std::is_invocable_v<decltype(std::views::enumerate), NotInvocable>);
+
+static_assert(std::is_same_v<decltype(std::ranges::views::enumerate), decltype(std::views::enumerate)>);
+
+constexpr bool test() {
+  // Test `views::enumerate_view(v)`
+  {
+    int buff[] = {0, 1, 2, 3};
+
+    using Result = std::ranges::enumerate_view<Range>;
+    Range const range(buff, buff + 4);
+
+    std::same_as<Result> decltype(auto) result = std::views::enumerate(range);
+    compareViews(result, {{0, 0}, {1, 1}, {2, 2}, {3, 3}});
+  }
+  {
+    std::string_view sv{"babazmt"};
+    using Result = std::ranges::enumerate_view<std::string_view>;
+
+    std::same_as<Result> decltype(auto) result = std::views::enumerate(sv);
+    compareViews(result, {{0, 'b'}, {1, 'a'}, {2, 'b'}, {3, 'a'}, {4, 'z'}, {5, 'm'}, {6, 't'}});
+  }
+  // Test `adaptor | views::enumerate`
+  {
+    int buff[] = {0, 1, 2, 3};
+
+    using Result = std::ranges::enumerate_view<Range>;
+    Range const range(buff, buff + 4);
+
+    std::same_as<Result> decltype(auto) result = range | std::views::enumerate;
+    compareViews(result, {{0, 0}, {1, 1}, {2, 2}, {3, 3}});
+  }
+  {
+    std::string_view sv{"babazmt"};
+    using Result = std::ranges::enumerate_view<std::string_view>;
+
+    std::same_as<Result> decltype(auto) result = sv | std::views::enumerate;
+    compareViews(result, {{0, 'b'}, {1, 'a'}, {2, 'b'}, {3, 'a'}, {4, 'z'}, {5, 'm'}, {6, 't'}});
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/base.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/base.pass.cpp
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr V base() const & requires copy_constructible<V> { return base_; }
+// constexpr V base() && { return std::move(base_); }
+
+#include <cassert>
+#include <ranges>
+
+#include "types.h"
+
+constexpr bool test() {
+  // Check the const& overload
+  {
+    int buff[] = {0, 1, 2, 3};
+
+    Range const range(buff, buff + 4);
+
+    std::ranges::enumerate_view<Range> const view{range};
+    std::same_as<Range> decltype(auto) result = view.base();
+    assert(result.wasCopyInitialized);
+    assert(range.begin() == result.begin());
+    assert(range.end() == result.end());
+  }
+  // Check the && overload
+  {
+    int buff[] = {0, 1, 2, 3};
+
+    Range const range(buff, buff + 4);
+
+    std::ranges::enumerate_view<Range> view{range};
+    std::same_as<Range> decltype(auto) result = std::move(view).base();
+    assert(result.wasMoveInitialized);
+    assert(range.begin() == result.begin());
+    assert(range.end() == result.end());
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/begin.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/begin.pass.cpp
@@ -0,0 +1,71 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr auto begin() requires (!simple-view<V>)
+// { return iterator<false>(ranges::begin(base_), 0); }
+// constexpr auto begin() const requires range-with-movable-references<const V>
+// { return iterator<true>(ranges::begin(base_), 0); }
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+#include "types.h"
+
+constexpr bool test() {
+  int buff[] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+  // Check the return type of `.begin()`
+  {
+    Range range(buff, buff + 1);
+
+    std::ranges::enumerate_view view(range);
+    using Iterator = std::ranges::iterator_t<decltype(view)>;
+    ASSERT_SAME_TYPE(Iterator, decltype(view.begin()));
+  }
+  // begin() over an empty range
+  {
+    Range range(buff, buff);
+
+    std::ranges::enumerate_view view(range);
+    auto it = view.begin();
+    assert(base(it.base()) == buff);
+    assert(it == view.end());
+  }
+  // begin() over a 1-element range
+  {
+    Range range(buff, buff + 1);
+
+    std::ranges::enumerate_view view(range);
+    auto it = view.begin();
+    assert(base(it.base()) == buff);
+  }
+  // begin() over a N-element range
+  {
+    Range range(buff, buff + 8);
+
+    std::ranges::enumerate_view view(range);
+    auto it = view.begin();
+    assert(base(it.base()) == buff);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/ctad.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/ctad.compile.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/ctad.compile.pass.cpp
@@ -0,0 +1,45 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// template<class R>
+//   enumerate_view(R&&) -> enumerate_view<views::all_t<R>>;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+#include "types.h"
+
+constexpr bool test() {
+  {
+    JustAView jv;
+    std::ranges::enumerate_view view(jv);
+    static_assert(std::is_same_v<decltype(view), std::ranges::enumerate_view<JustAView>>);
+  }
+  // Test with a range that isn't a view, to make sure we properly use views::all_t in the implementation.
+  {
+    NotAViewRange range;
+    std::ranges::enumerate_view view(range);
+    static_assert(std::is_same_v<decltype(view), std::ranges::enumerate_view<std::ranges::ref_view<NotAViewRange>>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/ctor.base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/ctor.base.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/ctor.base.pass.cpp
@@ -0,0 +1,97 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr explicit enumerate_view(V base);
+
+#include <ranges>
+
+#include <cassert>
+#include <tuple>
+#include <type_traits>
+
+constexpr int buff[] = {0, 1};
+
+struct DefaultConstructibleView : std::ranges::view_base {
+  constexpr DefaultConstructibleView() : begin_(buff), end_(buff + 1) {}
+  constexpr int const* begin() const { return begin_; }
+  constexpr int const* end() const { return end_; }
+
+  int const* begin_;
+  int const* end_;
+};
+
+template <bool DefaultInitializable>
+struct DefaultInitializableView : std::ranges::view_base {
+  constexpr explicit DefaultInitializableView()
+    requires DefaultInitializable
+  = default;
+
+  int* begin() const;
+  int* end() const;
+};
+
+struct NoDefaultView : std::ranges::view_base {
+  NoDefaultView() = delete;
+
+  int* begin() const;
+  int* end() const;
+};
+
+// SFINAE
+
+static_assert(!std::is_default_constructible_v<std::ranges::enumerate_view<NoDefaultView>>);
+static_assert(std::is_default_constructible_v<std::ranges::enumerate_view<DefaultInitializableView<true>>>);
+static_assert(!std::is_default_constructible_v<std::ranges::enumerate_view<DefaultInitializableView<false>>>);
+
+constexpr bool test() {
+  using EnumerateView = std::ranges::enumerate_view<DefaultConstructibleView>;
+
+  {
+    EnumerateView view;
+
+    assert((*view.begin() == std::tuple<std::ranges::iterator_t<EnumerateView>::difference_type, int>{0, 0}));
+    assert((*view.end() == std::tuple<std::ranges::iterator_t<EnumerateView>::difference_type, int>{1, 1}));
+
+    auto [bi, bv] = *view.begin();
+    assert(bi == 0);
+    assert(bv == 0);
+
+    auto [ei, ev] = *view.end();
+    assert(ei == 1);
+    assert(ev == 1);
+  }
+  {
+    EnumerateView view = {};
+
+    assert((*view.begin() == std::tuple<std::ranges::iterator_t<EnumerateView>::difference_type, int>{0, 0}));
+    assert((*view.end() == std::tuple<std::ranges::iterator_t<EnumerateView>::difference_type, int>{1, 1}));
+
+    auto [bi, bv] = *view.begin();
+    assert(bi == 0);
+    assert(bv == 0);
+
+    auto [ei, ev] = *view.end();
+    assert(ei == 1);
+    assert(ev == 1);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/ctor.default.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/ctor.default.pass.cpp
@@ -0,0 +1,97 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr enumerate_view() requires default_initializable<V> = default;
+
+#include <ranges>
+
+#include <cassert>
+#include <tuple>
+#include <type_traits>
+
+constexpr int buff[] = {0, 1};
+
+struct DefaultConstructibleView : std::ranges::view_base {
+  constexpr DefaultConstructibleView() : begin_(buff), end_(buff + 1) {}
+  constexpr int const* begin() const { return begin_; }
+  constexpr int const* end() const { return end_; }
+
+  int const* begin_;
+  int const* end_;
+};
+
+template <bool DefaultInitializable>
+struct DefaultInitializableView : std::ranges::view_base {
+  constexpr explicit DefaultInitializableView()
+    requires DefaultInitializable
+  = default;
+
+  int* begin() const;
+  int* end() const;
+};
+
+struct NoDefaultView : std::ranges::view_base {
+  NoDefaultView() = delete;
+
+  int* begin() const;
+  int* end() const;
+};
+
+// SFINAE
+
+static_assert(!std::is_default_constructible_v<std::ranges::enumerate_view<NoDefaultView>>);
+static_assert(std::is_default_constructible_v<std::ranges::enumerate_view<DefaultInitializableView<true>>>);
+static_assert(!std::is_default_constructible_v<std::ranges::enumerate_view<DefaultInitializableView<false>>>);
+
+constexpr bool test() {
+  using EnumerateView = std::ranges::enumerate_view<DefaultConstructibleView>;
+
+  {
+    EnumerateView view;
+
+    assert((*view.begin() == std::tuple<std::ranges::iterator_t<EnumerateView>::difference_type, int>{0, 0}));
+    assert((*view.end() == std::tuple<std::ranges::iterator_t<EnumerateView>::difference_type, int>{1, 1}));
+
+    auto [bi, bv] = *view.begin();
+    assert(bi == 0);
+    assert(bv == 0);
+
+    auto [ei, ev] = *view.end();
+    assert(ei == 1);
+    assert(ev == 1);
+  }
+  {
+    EnumerateView view = {};
+
+    assert((*view.begin() == std::tuple<std::ranges::iterator_t<EnumerateView>::difference_type, int>{0, 0}));
+    assert((*view.end() == std::tuple<std::ranges::iterator_t<EnumerateView>::difference_type, int>{1, 1}));
+
+    auto [bi, bv] = *view.begin();
+    assert(bi == 0);
+    assert(bv == 0);
+
+    auto [ei, ev] = *view.end();
+    assert(ei == 1);
+    assert(ev == 1);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/enable_borrowed_range.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/enable_borrowed_range.compile.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/enable_borrowed_range.compile.pass.cpp
@@ -0,0 +1,36 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// template<class View>
+//   constexpr bool enable_borrowed_range<enumerate_view<View>> =                    // freestanding
+//     enable_borrowed_range<View>;
+
+#include <cassert>
+#include <ranges>
+
+struct NonBorrowedRange : std::ranges::view_base {
+  int* begin();
+  int* end();
+};
+
+struct BorrowedRange : std::ranges::view_base {
+  int* begin();
+  int* end();
+};
+
+template <>
+inline constexpr bool std::ranges::enable_borrowed_range<BorrowedRange> = true;
+
+static_assert(!std::ranges::borrowed_range<std::ranges::enumerate_view<NonBorrowedRange>>);
+static_assert(std::ranges::borrowed_range<std::ranges::enumerate_view<BorrowedRange>>);
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/end.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/end.pass.cpp
@@ -0,0 +1,79 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr auto end() requires (!simple-view<V>) {
+//   if constexpr (common_range<V> && sized_range<V>)
+//     return iterator<false>(ranges::end(base_), ranges::distance(base_));
+//   else
+//     return sentinel<false>(ranges::end(base_));
+// }
+// constexpr auto end() const requires range-with-movable-references<const V> {
+//   if constexpr (common_range<const V> && sized_range<const V>)
+//     return iterator<true>(ranges::end(base_), ranges::distance(base_));
+//   else
+//     return sentinel<true>(ranges::end(base_));
+// }
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+#include "types.h"
+
+constexpr bool test() {
+  int buff[] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+  // Check the return type of `.end()`
+  {
+    Range range(buff, buff + 1);
+
+    std::ranges::enumerate_view view(range);
+    using Iterator = std::ranges::iterator_t<decltype(view)>;
+    ASSERT_SAME_TYPE(Iterator, decltype(view.end()));
+  }
+  // begin() over an empty range
+  {
+    Range range(buff, buff);
+
+    std::ranges::enumerate_view view(range);
+    auto it = view.begin();
+    assert(base(it.base()) == buff);
+    assert(it == view.end());
+  }
+  // begin() over a 1-element range
+  {
+    Range range(buff, buff + 1);
+
+    std::ranges::enumerate_view view(range);
+    auto it = view.end();
+    assert(base(it.base()) == buff + 1);
+  }
+  // begin() over a N-element range
+  {
+    Range range(buff, buff + 8);
+
+    std::ranges::enumerate_view view(range);
+    auto it = view.end();
+    assert(base(it.base()) == buff + 8);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/arithmetic.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/arithmetic.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/arithmetic.pass.cpp
@@ -0,0 +1,123 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr iterator& operator+=(difference_type x)
+//   requires random_access_range<Base>;
+// constexpr iterator& operator-=(difference_type x)
+//   requires random_access_range<Base>;
+
+// friend constexpr iterator operator+(const iterator& x, difference_type y)
+//   requires random_access_range<Base>;
+// friend constexpr iterator operator+(difference_type x, const iterator& y)
+//   requires random_access_range<Base>;
+// friend constexpr iterator operator-(const iterator& x, difference_type y)
+//   requires random_access_range<Base>;
+// friend constexpr difference_type operator-(const iterator& x, const iterator& y)
+
+#include <ranges>
+
+#include "test_iterators.h"
+
+// Concepts
+
+template <class T, class U>
+concept CanPlus = requires(T t, U u) { t + u; };
+
+template <class T, class U>
+concept CanPlusEqual = requires(T t, U u) { t += u; };
+
+template <class T, class U>
+concept CanMinus = requires(T t, U u) { t - u; };
+
+template <class T, class U>
+concept CanMinusEqual = requires(T t, U u) { t -= u; };
+
+template <class BaseRange>
+using EnumerateIter = std::ranges::iterator_t<std::ranges::enumerate_view<BaseRange>>;
+
+using RandomAccessRange = std::ranges::subrange<int*>;
+
+// SFINAE.
+
+static_assert(std::ranges::random_access_range<RandomAccessRange>);
+static_assert(
+    std::sized_sentinel_for<std::ranges::iterator_t<RandomAccessRange>, std::ranges::iterator_t<RandomAccessRange>>);
+
+static_assert(CanPlus<EnumerateIter<RandomAccessRange>, int>);
+static_assert(CanPlus<int, EnumerateIter<RandomAccessRange>>);
+static_assert(CanPlusEqual<EnumerateIter<RandomAccessRange>, int>);
+static_assert(CanMinus<EnumerateIter<RandomAccessRange>, int>);
+static_assert(CanMinus<EnumerateIter<RandomAccessRange>, EnumerateIter<RandomAccessRange>>);
+static_assert(CanMinusEqual<EnumerateIter<RandomAccessRange>, int>);
+
+using BidirectionalRange = std::ranges::subrange<bidirectional_iterator<int*>>;
+static_assert(!std::ranges::random_access_range<BidirectionalRange>);
+static_assert(
+    !std::sized_sentinel_for<std::ranges::iterator_t<BidirectionalRange>, std::ranges::iterator_t<BidirectionalRange>>);
+
+static_assert(!CanPlus<EnumerateIter<BidirectionalRange>, int>);
+static_assert(!CanPlus<int, EnumerateIter<BidirectionalRange>>);
+static_assert(!CanPlusEqual<EnumerateIter<BidirectionalRange>, int>);
+static_assert(!CanMinus<EnumerateIter<BidirectionalRange>, int>);
+static_assert(!CanMinusEqual<EnumerateIter<BidirectionalRange>, int>);
+
+constexpr bool test() {
+  int ts[] = {1, 2, 3, 4, 5};
+
+  RandomAccessRange r{&ts[0], &ts[0] + 5};
+  auto ev = r | std::views::enumerate;
+
+  // operator+(x, n) operator+(n,x) and operator+=
+  {
+    auto it1 = ev.begin();
+
+    auto it2 = it1 + 3;
+    assert(it2.base() == &ts[3]);
+
+    auto it3 = 3 + it1;
+    assert(it3.base() == &ts[3]);
+
+    it1 += 3;
+    assert(it1 == it2);
+    assert(it1.base() == &ts[3]);
+  }
+  // operator-(x, n) and operator-=
+  {
+    auto it1 = ev.end();
+
+    auto it2 = it1 - 4;
+    assert(it2.base() == &ts[1]);
+
+    it1 -= 4;
+    assert(it1 == it2);
+    assert(it1.base() == &ts[1]);
+  }
+  // operator-(x, y)
+  {
+    assert((ev.end() - ev.begin()) == 5);
+
+    auto it1 = ev.begin() + 2;
+    auto it2 = ev.end() - 2;
+    assert((it1 - it2) == -1);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/base.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/base.pass.cpp
@@ -0,0 +1,76 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr sentinel_t<Base> base() const;
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <utility>
+
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View              = MinimalView<Iterator, Sentinel>;
+  using EnumerateView     = std::ranges::enumerate_view<View>;
+  using EnumerateIterator = std::ranges::iterator_t<EnumerateView>;
+
+  auto make_enumerate_view = [](auto begin, auto end) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+
+    return EnumerateView(std::move(view));
+  };
+
+  std::array<int, 5> array{0, 1, 2, 3, 84};
+  const auto view = make_enumerate_view(array.begin(), array.end());
+
+  // Test the const& version
+  {
+    EnumerateIterator const it                          = view.begin();
+    std::same_as<const Iterator&> decltype(auto) result = it.base();
+    ASSERT_NOEXCEPT(it.base());
+    assert(base(base(result)) == array.begin());
+  }
+
+  // Test the && version
+  {
+    EnumerateIterator it                         = view.begin();
+    std::same_as<Iterator> decltype(auto) result = std::move(it).base();
+    assert(base(base(result)) == array.begin());
+  }
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+  test<int const*>();
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/compare.three_way.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/compare.three_way.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/compare.three_way.pass.cpp
@@ -0,0 +1,101 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// friend constexpr strong_ordering operator<=>(const iterator& x, const iterator& y) noexcept;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+#include "../types.h"
+
+constexpr void compareOperatorTest(const auto& iter1, const auto& iter2) {
+  assert(!(iter1 < iter1));
+  assert(iter1 < iter2);
+  assert(!(iter2 < iter1));
+
+  assert(iter1 <= iter1);
+  assert(iter1 <= iter2);
+  assert(!(iter2 <= iter1));
+
+  assert(!(iter1 > iter1));
+  assert(!(iter1 > iter2));
+  assert(iter2 > iter1);
+
+  assert(iter1 >= iter1);
+  assert(!(iter1 >= iter2));
+  assert(iter2 >= iter1);
+
+  assert(iter1 == iter1);
+  assert(!(iter1 == iter2));
+  assert(iter2 == iter2);
+
+  assert(!(iter1 != iter1));
+  assert(iter1 != iter2);
+  assert(!(iter2 != iter2));
+}
+
+template <class T, class U>
+concept HasSpaceship = requires(T t, U u) { t <=> u; };
+
+constexpr bool test() {
+  int buff[] = {0, 1, 2, 3};
+  {
+    using View = std::ranges::enumerate_view<Range>;
+    Range const range(buff, buff + 4);
+
+    std::same_as<View> decltype(auto) ev = std::views::enumerate(range);
+
+    using Iterator = decltype(ev.begin());
+    static_assert(HasSpaceship<Iterator, Iterator>);
+
+    auto it1 = ev.begin();
+    auto it2 = it1 + 1;
+
+    compareOperatorTest(it1, it2);
+
+    assert((it1 <=> it2) == std::strong_ordering::less);
+    assert((it1 <=> it1) == std::strong_ordering::equal);
+    assert((it2 <=> it2) == std::strong_ordering::equal);
+    assert((it2 <=> it1) == std::strong_ordering::greater);
+  }
+  {
+    // Test an old-school iterator with no operator<=>
+    using Iterator = random_access_iterator<int*>;
+    using Subrange = std::ranges::subrange<Iterator>;
+    static_assert(!std::three_way_comparable<Iterator>);
+    using EnumerateView = std::ranges::enumerate_view<Subrange>;
+    static_assert(std::three_way_comparable<std::ranges::iterator_t<EnumerateView>>);
+
+    auto ev  = Subrange{Iterator{&buff[0]}, Iterator{&buff[0] + 3}} | std::views::enumerate;
+    auto it1 = ev.begin();
+    auto it2 = it1 + 1;
+
+    compareOperatorTest(it1, it2);
+
+    assert((it1 <=> it2) == std::strong_ordering::less);
+    assert((it1 <=> it1) == std::strong_ordering::equal);
+    assert((it2 <=> it2) == std::strong_ordering::equal);
+    assert((it2 <=> it1) == std::strong_ordering::greater);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/ctor.convert.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/ctor.convert.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/ctor.convert.pass.cpp
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr iterator(iterator<!Const> i)
+//   requires Const && convertible_to<iterator_t<V>, iterator_t<Base>>;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+
+constexpr bool test() { return true; }
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/ctor.default.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/ctor.default.pass.cpp
@@ -0,0 +1,66 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// iterator() requires default_initializable<iterator_t<Base>> = default;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, bool IsNoexcept = true>
+constexpr void test_default_constructible() {
+  using View              = MinimalView<Iterator, sentinel_wrapper<Iterator>>;
+  using EnumerateView     = std::ranges::enumerate_view<View>;
+  using EnumerateIterator = std::ranges::iterator_t<EnumerateView>;
+
+  EnumerateIterator it1;
+  EnumerateIterator it2{};
+
+  assert(it1 == it2);
+
+  static_assert(noexcept(EnumerateIterator()) == IsNoexcept);
+}
+
+template <class Iterator>
+constexpr void test_not_default_constructible() {
+  // Make sure the iterator is *not* default constructible when the underlying iterator isn't.
+  using View              = MinimalView<Iterator, sentinel_wrapper<Iterator>>;
+  using EnumerateView     = std::ranges::enumerate_view<View>;
+  using EnumerateIterator = std::ranges::iterator_t<EnumerateView>;
+
+  static_assert(!std::is_default_constructible_v<EnumerateIterator>);
+}
+
+constexpr bool tests() {
+  // clang-format off
+  test_not_default_constructible<cpp17_input_iterator<int*>>();
+  test_not_default_constructible<cpp20_input_iterator<int*>>();
+  test_default_constructible<forward_iterator<int*>,       /* noexcept */ false>();
+  test_default_constructible<bidirectional_iterator<int*>, /* noexcept */ false>();
+  test_default_constructible<random_access_iterator<int*>, /* noexcept */ false>();
+  test_default_constructible<contiguous_iterator<int*>,    /* noexcept */ false>();
+  test_default_constructible<int*,                         /* noexcept */ true>();
+  // clang-format on
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/deref.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/deref.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/deref.pass.cpp
@@ -0,0 +1,86 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr auto operator*() const {
+//   return reference-type(pos_, *current_);
+// }
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <cstddef>
+#include <utility>
+#include <tuple>
+
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+template <class Iterator, class ValueType = int, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View              = MinimalView<Iterator, Sentinel>;
+  using EnumerateView     = std::ranges::enumerate_view<View>;
+  using EnumerateIterator = std::ranges::iterator_t<EnumerateView>;
+
+  using Result = std::tuple<typename EnumerateIterator::difference_type,
+                            std::ranges::range_reference_t<MinimalView<Iterator, Sentinel>>>;
+
+  auto make_enumerate_view = [](auto begin, auto end) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return EnumerateView(std::move(view));
+  };
+
+  std::array array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+  EnumerateView view = make_enumerate_view(array.begin(), array.end());
+
+  auto it = view.begin();
+  for (std::size_t index = 0; index < array.size(); ++index) {
+    std::same_as<Result> decltype(auto) result = *it;
+
+    auto [resultIndex, resultValue] = result;
+    assert(std::cmp_equal(index, resultIndex));
+    assert(array[index] == resultValue);
+
+    ++it;
+  }
+
+  assert(it == view.end());
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+
+  test<cpp17_input_iterator<int const*>, int const>();
+  test<cpp20_input_iterator<int const*>, int const>();
+  test<forward_iterator<int const*>, int const>();
+  test<bidirectional_iterator<int const*>, int const>();
+  test<random_access_iterator<int const*>, int const>();
+  test<contiguous_iterator<int const*>, int const>();
+  test<int const*, int const>();
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/equal.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/equal.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/equal.pass.cpp
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// friend constexpr bool operator==(const iterator& x, const iterator& y) noexcept;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+
+constexpr bool test() { return true; }
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/index.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/index.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/index.pass.cpp
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr difference_type index() const noexcept;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+
+constexpr bool test() { return true; }
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/iter_move.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/iter_move.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/iter_move.pass.cpp
@@ -0,0 +1,80 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// friend constexpr auto iter_move(const iterator& i)
+//   noexcept(noexcept(ranges::iter_move(i.current_)) &&
+//             is_nothrow_move_constructible_v<range_rvalue_reference_t<Base>>) {
+//   return tuple<difference_type,
+//                 range_rvalue_reference_t<Base>>(i.pos_, ranges::iter_move(i.current_));
+// }
+
+#include <ranges>
+#include <array>
+#include <cassert>
+#include <utility>
+
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+template <class Iterator, bool HasNoexceptIterMove>
+constexpr void test() {
+  using Sentinel          = sentinel_wrapper<Iterator>;
+  using View              = MinimalView<Iterator, Sentinel>;
+  using EnumerateView     = std::ranges::enumerate_view<View>;
+  using EnumerateIterator = std::ranges::iterator_t<EnumerateView>;
+
+  auto make_enumerate_view = [](auto begin, auto end) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+
+    return EnumerateView(std::move(view));
+  };
+
+  std::array<int, 5> array{0, 1, 2, 3, 4};
+  EnumerateView view         = make_enumerate_view(array.begin(), array.end());
+  EnumerateIterator const it = view.begin();
+
+  auto&& result = iter_move(it);
+
+  static_assert(std::is_same_v<decltype(result),
+                               std::tuple<typename std::ranges::iterator_t<EnumerateView>::difference_type, int&&>&&>);
+  static_assert(std::is_same_v<decltype(result), std::tuple<typename decltype(it)::difference_type, int&&>&&>);
+
+  assert(get<0>(result) == 0);
+  assert(&get<1>(result) == array.begin());
+
+  static_assert(noexcept(iter_move(it)) == HasNoexceptIterMove);
+}
+
+constexpr bool tests() {
+  // clang-format off
+  test<cpp17_input_iterator<int*>,           /* noexcept */ false>();
+  test<cpp20_input_iterator<int*>,           /* noexcept */ false>();
+  test<forward_iterator<int*>,               /* noexcept */ false>();
+  test<bidirectional_iterator<int*>,         /* noexcept */ false>();
+  test<random_access_iterator<int*>,         /* noexcept */ false>();
+  test<contiguous_iterator<int*>,            /* noexcept */ false>();
+  test<int*,                                 /* noexcept */ true>();
+  test<NoexceptIterMoveInputIterator<true>,  /* noexcept */ true>();
+  test<NoexceptIterMoveInputIterator<false>, /* noexcept */ false>();
+  // clang-format on
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/subscript.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/subscript.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/iterator/subscript.pass.cpp
@@ -0,0 +1,78 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr auto operator[](difference_type n) const
+//   requires random_access_range<Base>
+// { return reference-type(pos_ + n, current_[n]); }
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+
+#include "test_iterators.h"
+
+template <class T, class U>
+concept HasSubscriptOperator = requires(T t, U u) { t[u]; };
+
+template <class BaseRange>
+using EnumerateIterator = std::ranges::iterator_t<std::ranges::enumerate_view<BaseRange>>;
+
+using RandomAccessRange = std::ranges::subrange<int*>;
+static_assert(std::ranges::random_access_range<RandomAccessRange>);
+
+static_assert(HasSubscriptOperator<EnumerateIterator<RandomAccessRange>, int>);
+
+using BidirectionalRange = std::ranges::subrange<bidirectional_iterator<int*>>;
+static_assert(!std::ranges::random_access_range<BidirectionalRange>);
+
+static_assert(!HasSubscriptOperator<EnumerateIterator<BidirectionalRange>, int>);
+
+constexpr bool test() {
+  // Reference
+  {
+    int ts[]  = {0, 1, 2, 3, 84};
+    auto view = ts | std::views::enumerate;
+    auto it   = view.begin();
+
+    assert(std::get<0>(it[0]) == std::get<0>(*it));
+    assert(std::get<1>(it[0]) == std::get<1>(*it));
+    assert(std::get<0>(it[2]) == std::get<0>(*(it + 2)));
+    assert(std::get<1>(it[2]) == std::get<1>(*(it + 2)));
+    assert(std::get<0>(it[4]) == std::get<0>(*(it + 4)));
+    assert(std::get<1>(it[4]) == std::get<1>(*(it + 4)));
+
+    static_assert(std::is_same_v<decltype(it[2]), std::tuple<decltype(it)::difference_type, int&>>);
+  }
+
+  // Value
+  {
+    auto view = std::views::iota(0, 5) | std::views::enumerate;
+    auto it   = view.begin();
+
+    assert(it[0] == *it);
+    assert(it[2] == *(it + 2));
+    assert(it[4] == *(it + 4));
+
+    static_assert(std::is_same_v<decltype(it[2]), std::tuple<decltype(it)::difference_type, int>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/base.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/base.pass.cpp
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr sentinel_t<Base> base() const;
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <utility>
+
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View              = MinimalView<Iterator, Sentinel>;
+  using EnumerateView     = std::ranges::enumerate_view<View>;
+  using EnumerateSentinel = std::ranges::sentinel_t<EnumerateView>;
+
+  auto make_enumerate_view = [](auto begin, auto end) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+
+    return EnumerateView(std::move(view));
+  };
+
+  std::array<int, 5> array{0, 1, 2, 3, 84};
+  auto view = make_enumerate_view(array.begin(), array.end());
+
+  EnumerateSentinel const s                    = view.end();
+  std::same_as<Sentinel> decltype(auto) result = s.base();
+  assert(base(base(result)) == array.end());
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/ctor.convert.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/ctor.convert.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/ctor.convert.pass.cpp
@@ -0,0 +1,79 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+//  constexpr sentinel(sentinel<!Const> other)
+//       requires Const && convertible_to<sentinel_t<V>, sentinel_t<Base>>;
+
+// #include <cassert>
+// #include <ranges>
+// #include <tuple>
+
+// #include "../types.h"
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <utility>
+
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View                   = MinimalView<Iterator, Sentinel>;
+  using EnumerateView          = std::ranges::enumerate_view<View>;
+  using EnumerateSentinel      = std::ranges::sentinel_t<EnumerateView>;
+  using EnumerateConstSentinel = std::ranges::sentinel_t<const EnumerateView>;
+
+  auto make_enumerate_view = [](auto begin, auto end) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+
+    return EnumerateView(std::move(view));
+  };
+
+  static_assert(std::is_convertible_v<EnumerateSentinel, EnumerateConstSentinel>);
+
+  std::array<int, 5> array{0, 1, 2, 3, 84};
+  auto view = make_enumerate_view(array.begin(), array.end());
+
+  std::same_as<EnumerateSentinel> decltype(auto) s = view.end();
+  std::same_as<Sentinel> decltype(auto) sResult    = s.base();
+  assert(base(base(sResult)) == array.end());
+
+  // Test assignment
+  EnumerateConstSentinel cs                      = s;
+  std::same_as<Sentinel> decltype(auto) csResult = cs.base();
+  assert(base(base(csResult)) == array.end());
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/ctor.default.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/ctor.default.pass.cpp
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// iterator() requires default_initializable<iterator_t<Base>> = default;
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View              = MinimalView<Iterator, Sentinel>;
+  using EnumerateView     = std::ranges::enumerate_view<View>;
+  using EnumerateSentinel = std::ranges::sentinel_t<EnumerateView>;
+
+  EnumerateSentinel s1;
+  EnumerateSentinel s2{};
+
+  assert(base(base(s1.base())) == base(base(s2.base())));
+
+  static_assert(noexcept(EnumerateSentinel()));
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/equal.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/equal.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/equal.pass.cpp
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// template<bool OtherConst>
+//   requires sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
+// friend constexpr bool operator==(const iterator<OtherConst>& x, const sentinel& y);
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <utility>
+
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View = MinimalView<Iterator, Sentinel>;
+
+  std::array<int, 5> array{0, 1, 2, 3, 84};
+
+  View v(Iterator(array.begin()), Sentinel(Iterator(array.end())));
+  std::ranges::enumerate_view view(std::move(v));
+
+  auto const it = view.begin();
+  auto const s  = view.end();
+
+  std::same_as<bool> decltype(auto) eqResult = (it == s);
+  assert(!eqResult);
+  std::same_as<bool> decltype(auto) neqResult = (it != s);
+  assert(neqResult);
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/minus.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/minus.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/sentinel/minus.pass.cpp
@@ -0,0 +1,226 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// template<bool OtherConst>
+//   requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
+// friend constexpr range_difference_t<maybe-const<OtherConst, V>>
+//   operator-(const iterator<OtherConst>& x, const sentinel& y);
+
+// template<bool OtherConst>
+//   requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
+// friend constexpr range_difference_t<maybe-const<OtherConst, V>>
+//   operator-(const sentinel& x, const iterator<OtherConst>& y);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+
+template <bool Const>
+struct Iter {
+  int* it_;
+
+  using value_type       = int;
+  using difference_type  = std::ptrdiff_t;
+  using iterator_concept = std::input_iterator_tag;
+
+  constexpr decltype(auto) operator*() const { return *it_; }
+  constexpr Iter& operator++() {
+    ++it_;
+    return *this;
+  }
+  constexpr void operator++(int) { ++it_; }
+};
+
+template <bool Const>
+struct Sent {
+  int* end_;
+
+  constexpr bool operator==(const Iter<Const>& i) const { return i.it_ == end_; }
+};
+
+template <bool Const>
+struct SizedSent {
+  int* end_;
+
+  constexpr bool operator==(const Iter<Const>& i) const { return i.it_ == end_; }
+
+  friend constexpr auto operator-(const SizedSent& st, const Iter<Const>& it) { return st.end_ - it.it_; }
+
+  friend constexpr auto operator-(const Iter<Const>& it, const SizedSent& st) { return it.it_ - st.end_; }
+};
+
+template <bool Const>
+struct CrossSizedSent {
+  int* end_;
+
+  template <bool C>
+  constexpr bool operator==(const Iter<C>& i) const {
+    return i.it_ == end_;
+  }
+
+  template <bool C>
+  friend constexpr auto operator-(const CrossSizedSent& st, const Iter<C>& it) {
+    return st.end_ - it.it_;
+  }
+
+  template <bool C>
+  friend constexpr auto operator-(const Iter<C>& it, const CrossSizedSent& st) {
+    return it.it_ - st.end_;
+  }
+};
+
+template <class T>
+struct BufferView : std::ranges::view_base {
+  T* buffer_;
+  std::size_t size_;
+
+  template <std::size_t N>
+  constexpr BufferView(T (&b)[N]) : buffer_(b), size_(N) {}
+
+  template <std::size_t N>
+  constexpr BufferView(std::array<T, N>& arr) : buffer_(arr.data()), size_(N) {}
+};
+
+template <template <bool> class It, template <bool> class St>
+struct Range : BufferView<int> {
+  using BufferView<int>::BufferView;
+
+  using iterator       = It<false>;
+  using sentinel       = St<false>;
+  using const_iterator = It<true>;
+  using const_sentinel = St<true>;
+
+  constexpr iterator begin() { return {buffer_}; }
+  constexpr const_iterator begin() const { return {buffer_}; }
+  constexpr sentinel end() { return sentinel{buffer_ + size_}; }
+  constexpr const_sentinel end() const { return const_sentinel{buffer_ + size_}; }
+};
+
+template <class T, class U>
+concept HasMinus = requires(const T t, const U u) { t - u; };
+
+template <class BaseRange>
+using EnumerateView = std::ranges::enumerate_view<BaseRange>;
+
+template <class BaseRange>
+using EnumerateIter = std::ranges::iterator_t<EnumerateView<BaseRange>>;
+
+template <class BaseRange>
+using EnumerateConstIter = std::ranges::iterator_t<const EnumerateView<BaseRange>>;
+
+template <class BaseRange>
+using EnumerateSentinel = std::ranges::sentinel_t<EnumerateView<BaseRange>>;
+
+template <class BaseRange>
+using EnumerateConstSentinel = std::ranges::sentinel_t<const EnumerateView<BaseRange>>;
+
+constexpr void testConstraints() {
+  // Base is not sized
+  {
+    using Base = Range<Iter, Sent>;
+    static_assert(!HasMinus<EnumerateSentinel<Base>, EnumerateIter<Base>>);
+    static_assert(!HasMinus<EnumerateIter<Base>, EnumerateSentinel<Base>>);
+
+    static_assert(!HasMinus<EnumerateSentinel<Base>, EnumerateConstIter<Base>>);
+    static_assert(!HasMinus<EnumerateConstIter<Base>, EnumerateSentinel<Base>>);
+
+    static_assert(!HasMinus<EnumerateConstSentinel<Base>, EnumerateConstIter<Base>>);
+    static_assert(!HasMinus<EnumerateConstIter<Base>, EnumerateConstSentinel<Base>>);
+
+    static_assert(!HasMinus<EnumerateConstSentinel<Base>, EnumerateIter<Base>>);
+    static_assert(!HasMinus<EnumerateIter<Base>, EnumerateConstSentinel<Base>>);
+  }
+
+  // Base is sized but not cross const
+  {
+    using Base = Range<Iter, SizedSent>;
+    static_assert(HasMinus<EnumerateSentinel<Base>, EnumerateIter<Base>>);
+    static_assert(HasMinus<EnumerateIter<Base>, EnumerateSentinel<Base>>);
+
+    static_assert(!HasMinus<EnumerateSentinel<Base>, EnumerateConstIter<Base>>);
+    static_assert(!HasMinus<EnumerateConstIter<Base>, EnumerateSentinel<Base>>);
+
+    static_assert(HasMinus<EnumerateConstSentinel<Base>, EnumerateConstIter<Base>>);
+    static_assert(HasMinus<EnumerateConstIter<Base>, EnumerateConstSentinel<Base>>);
+
+    static_assert(!HasMinus<EnumerateConstSentinel<Base>, EnumerateIter<Base>>);
+    static_assert(!HasMinus<EnumerateIter<Base>, EnumerateConstSentinel<Base>>);
+  }
+
+  // Base is cross const sized
+  {
+    using Base = Range<Iter, CrossSizedSent>;
+    static_assert(HasMinus<EnumerateSentinel<Base>, EnumerateIter<Base>>);
+    static_assert(HasMinus<EnumerateIter<Base>, EnumerateSentinel<Base>>);
+
+    static_assert(HasMinus<EnumerateSentinel<Base>, EnumerateConstIter<Base>>);
+    static_assert(HasMinus<EnumerateConstIter<Base>, EnumerateSentinel<Base>>);
+
+    static_assert(HasMinus<EnumerateConstSentinel<Base>, EnumerateConstIter<Base>>);
+    static_assert(HasMinus<EnumerateConstIter<Base>, EnumerateConstSentinel<Base>>);
+
+    static_assert(HasMinus<EnumerateConstSentinel<Base>, EnumerateIter<Base>>);
+    static_assert(HasMinus<EnumerateIter<Base>, EnumerateConstSentinel<Base>>);
+  }
+}
+
+constexpr bool test() {
+  int buffer[] = {1, 2, 3, 4, 5};
+
+  // Base is sized but not cross const
+  {
+    using Base = Range<Iter, SizedSent>;
+    Base base{buffer};
+    auto ev         = base | std::views::enumerate;
+    auto iter       = ev.begin();
+    auto const_iter = std::as_const(ev).begin();
+    auto sent       = ev.end();
+    auto const_sent = std::as_const(ev).end();
+
+    assert(iter - sent == -5);
+    assert(sent - iter == 5);
+    assert(const_iter - const_sent == -5);
+    assert(const_sent - const_iter == 5);
+  }
+
+  // Base is cross const sized
+  {
+    using Base = Range<Iter, CrossSizedSent>;
+    Base base{buffer};
+    auto ev         = base | std::views::enumerate;
+    auto iter       = ev.begin();
+    auto const_iter = std::as_const(ev).begin();
+    auto sent       = ev.end();
+    auto const_sent = std::as_const(ev).end();
+
+    assert(iter - sent == -5);
+    assert(sent - iter == 5);
+    assert(iter - const_sent == -5);
+    assert(const_sent - iter == 5);
+    assert(const_iter - sent == -5);
+    assert(sent - const_iter == 5);
+    assert(const_iter - const_sent == -5);
+    assert(const_sent - const_iter == 5);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/size.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.enumerate/size.pass.cpp
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/size.pass.cpp
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20
+
+// <ranges>
+
+// class enumerate_view
+
+// constexpr auto size() requires sized_range<V>
+// { return ranges::size(base_); }
+// constexpr auto size() const requires sized_range<const V>
+// { return ranges::size(base_); }
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+#include "types.h"
+
+template <class T>
+concept HasSize = requires(T t) { t.size(); };
+
+static_assert(HasSize<std::ranges::enumerate_view<SizedRange>>);
+static_assert(HasSize<const std::ranges::enumerate_view<SizedRange>>);
+
+static_assert(std::ranges::sized_range<Range>);
+
+struct NonSizedRange : std::ranges::view_base {
+  using iterator = forward_iterator<int*>;
+  iterator begin() const;
+  iterator end() const;
+};
+
+static_assert(!std::ranges::sized_range<NonSizedRange>);
+static_assert(!std::ranges::sized_range<const NonSizedRange>);
+
+static_assert(!HasSize<std::ranges::enumerate_view<NonSizedRange>>);
+static_assert(!HasSize<const std::ranges::enumerate_view<NonSizedRange>>);
+
+constexpr bool test() {
+  int buffer[] = {1, 2, 3};
+
+  // Non-const and const are sized
+  {
+    auto view = std::views::enumerate(buffer);
+    assert(view.size() == 3);
+    assert(std::as_const(view).size() == 3);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.enumerate/types.h b/libcxx/test/std/ranges/range.adaptors/range.enumerate/types.h
new file mode 100644
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.enumerate/types.h
@@ -0,0 +1,129 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_ENUMERATE_TYPES_H
+#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_ENUMERATE_TYPES_H
+
+#include <ranges>
+
+#include "test_iterators.h"
+
+// Types
+
+struct Range : std::ranges::view_base {
+  using Iterator = cpp20_input_iterator<int*>;
+  using Sentinel = sentinel_wrapper<Iterator>;
+
+  constexpr explicit Range(int* b, int* e) : begin_(b), end_(e) {}
+  constexpr Range(Range const& other) : begin_(other.begin_), end_(other.end_), wasCopyInitialized(true) {}
+  constexpr Range(Range&& other) : begin_(other.begin_), end_(other.end_), wasMoveInitialized(true) {}
+  Range& operator=(Range const&) = default;
+  Range& operator=(Range&&)      = default;
+
+  constexpr int* begin() const { return begin_; }
+  constexpr int* end() const { return end_; }
+
+  int* begin_;
+  int* end_;
+
+  bool wasCopyInitialized = false;
+  bool wasMoveInitialized = false;
+};
+
+static_assert(std::ranges::__range_with_movable_references<Range>);
+static_assert(std::ranges::range<Range> && std::ranges::view<Range>);
+
+struct SizedRange : public Range {
+  std::size_t size_;
+
+  constexpr std::size_t size() { return size_; }
+};
+
+struct JustAView : std::ranges::view_base {
+  JustAView() = default;
+
+  forward_iterator<int*> begin() const;
+  sentinel_wrapper<forward_iterator<int*>> end() const;
+};
+
+static_assert(std::ranges::view<JustAView>);
+
+template <class Iterator, class Sentinel>
+struct MinimalView : std::ranges::view_base {
+  constexpr explicit MinimalView(Iterator it, Sentinel sent) : it_(base(std::move(it))), sent_(base(std::move(sent))) {}
+
+  MinimalView(MinimalView&&)            = default;
+  MinimalView& operator=(MinimalView&&) = default;
+
+  constexpr Iterator begin() const { return Iterator(it_); }
+  constexpr Sentinel end() const { return Sentinel(sent_); }
+
+private:
+  decltype(base(std::declval<Iterator>())) it_;
+  decltype(base(std::declval<Sentinel>())) sent_;
+};
+
+struct NotInvocable {};
+
+struct NotAView {};
+
+static_assert(!std::ranges::view<NotAView>);
+
+struct NotAViewRange {
+  using Iterator = cpp20_input_iterator<int*>;
+  using Sentinel = sentinel_wrapper<Iterator>;
+
+  NotAViewRange() = default;
+  constexpr explicit NotAViewRange(int* b, int* e) : begin_(b), end_(e) {}
+  constexpr NotAViewRange(NotAViewRange const& other) = default;
+  constexpr NotAViewRange(NotAViewRange&& other)      = default;
+  NotAViewRange& operator=(NotAViewRange const&)      = default;
+  NotAViewRange& operator=(NotAViewRange&&)           = default;
+
+  constexpr int* begin() const { return begin_; }
+  constexpr int* end() const { return end_; }
+
+  int* begin_;
+  int* end_;
+};
+
+static_assert(std::ranges::range<NotAViewRange> && !std::ranges::view<NotAViewRange>);
+
+template <bool IsNoexcept>
+class NoexceptIterMoveInputIterator {
+  int* it_;
+
+public:
+  using iterator_category = std::input_iterator_tag;
+  using value_type        = int;
+  using difference_type   = typename std::iterator_traits<int*>::difference_type;
+  using pointer           = int*;
+  using reference         = int&;
+
+  NoexceptIterMoveInputIterator() = default;
+  explicit constexpr NoexceptIterMoveInputIterator(int* it) : it_(it) {}
+
+  friend constexpr decltype(auto) iter_move(const NoexceptIterMoveInputIterator& it) noexcept(IsNoexcept) {
+    return std::ranges::iter_move(it.it_);
+  }
+
+  friend constexpr int* base(const NoexceptIterMoveInputIterator& i) { return i.it_; }
+
+  constexpr reference operator*() const { return *it_; }
+  constexpr NoexceptIterMoveInputIterator& operator++() {
+    ++it_;
+    return *this;
+  }
+  constexpr NoexceptIterMoveInputIterator operator++(int) {
+    NoexceptIterMoveInputIterator tmp(*this);
+    ++(*this);
+    return tmp;
+  }
+};
+
+#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_ENUMERATE_TYPES_H
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -804,6 +804,11 @@
             "headers": ["ranges"],
             "unimplemented": True,
         },
+        {
+            "name": "__cpp_lib_ranges_enumerate",
+            "values": {"c++23": 202302},
+            "headers": ["ranges"],
+        },
         {
             "name": "__cpp_lib_ranges_iota",
             "values": {"c++23": 202202},