[libcxx-commits] [libcxx] 9446182 - [libc++][ranges] implement `std::views::elements_view`

via libcxx-commits libcxx-commits at lists.llvm.org
Sun Jan 15 09:36:41 PST 2023


Author: Hui Xie
Date: 2023-01-15T17:36:10Z
New Revision: 94461822c75d5080bf648f86552f7a59b76905c9

URL: https://github.com/llvm/llvm-project/commit/94461822c75d5080bf648f86552f7a59b76905c9
DIFF: https://github.com/llvm/llvm-project/commit/94461822c75d5080bf648f86552f7a59b76905c9.diff

LOG: [libc++][ranges] implement `std::views::elements_view`

`subrange` is also a `tuple-like`. To avoid the add entire `subrange` dependencies to `tuple-like`, we need forward declaration of `subrange`. However, the class template constraints of `subrange` currently requires `__iterator/concepts.h`, which requires `<concepts>`. The problem is that currently `tuple-like` is used in several different places, including libc++ extension for pair constructors. we don't want to add `<concepts>` to pair and other stuff. So this change also created several small headers that `subrange`'s declaration needed inside `__iterator/concepts/`

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

Added: 
    libcxx/include/__fwd/subrange.h
    libcxx/include/__ranges/elements_view.h
    libcxx/include/__tuple_dir/pair_like.h
    libcxx/include/__tuple_dir/tuple_like_ext.h
    libcxx/test/libcxx/ranges/range.adaptors/range.elements/elements_view.no_unique_address.compile.pass.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.elements/sentinel.no_unique_address.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/adaptor.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/base.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/begin.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/borrowed.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/ctor.default.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/ctor.view.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/end.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/general.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/iterator/arithmetic.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/iterator/base.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/iterator/compare.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.base.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.default.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.other.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/iterator/decrement.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/iterator/deref.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/iterator/increment.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/iterator/member_types.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/iterator/subscript.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/range.concept.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/base.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.base.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.convert.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.default.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/equality.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/minus.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/size.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.elements/types.h

Modified: 
    libcxx/docs/ReleaseNotes.rst
    libcxx/docs/Status/Cxx20Issues.csv
    libcxx/docs/Status/Cxx20Papers.csv
    libcxx/docs/Status/Cxx2bIssues.csv
    libcxx/docs/Status/SpaceshipProjects.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__fwd/get.h
    libcxx/include/__ranges/subrange.h
    libcxx/include/__tuple_dir/sfinae_helpers.h
    libcxx/include/__tuple_dir/tuple_like.h
    libcxx/include/module.modulemap.in
    libcxx/include/ranges
    libcxx/include/tuple
    libcxx/test/libcxx/private_headers.verify.cpp
    llvm/utils/gn/secondary/libcxx/include/BUILD.gn

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/ReleaseNotes.rst b/libcxx/docs/ReleaseNotes.rst
index 40c59517202fe..6f08d95c22a3e 100644
--- a/libcxx/docs/ReleaseNotes.rst
+++ b/libcxx/docs/ReleaseNotes.rst
@@ -66,6 +66,7 @@ Implemented Papers
 - P0415R1 - ``constexpr`` for ``std::complex``
 - P1208R6 - ``std::source_location``
 - P0323R12 - ``std::expected``
+- P1035R7 - Input Range Adaptors
 
 Improvements and New Features
 -----------------------------

diff  --git a/libcxx/docs/Status/Cxx20Issues.csv b/libcxx/docs/Status/Cxx20Issues.csv
index d023df674c23b..48bc936ba02c2 100644
--- a/libcxx/docs/Status/Cxx20Issues.csv
+++ b/libcxx/docs/Status/Cxx20Issues.csv
@@ -281,7 +281,7 @@
 "`3373 <https://wg21.link/LWG3373>`__","``{to,from}_chars_result``\  and ``format_to_n_result``\  need the  ""we really mean what we say"" wording","Prague","|Complete|","14.0","|format|"
 "`3374 <https://wg21.link/LWG3374>`__","P0653 + P1006 should have made the other ``std::to_address``\  overload ``constexpr``\ ","Prague","|Complete|","12.0"
 "`3375 <https://wg21.link/LWG3375>`__","``decay``\  in ``viewable_range``\  should be ``remove_cvref``\ ","Prague","|Complete|","15.0","|ranges|"
-"`3377 <https://wg21.link/LWG3377>`__","``elements_view::iterator``\  befriends a specialization of itself","Prague","","","|ranges|"
+"`3377 <https://wg21.link/LWG3377>`__","``elements_view::iterator``\  befriends a specialization of itself","Prague","|Nothing To Do|","","|ranges|"
 "`3379 <https://wg21.link/LWG3379>`__","""``safe``\ "" in several library names is misleading","Prague","|Complete|","15.0","|ranges|"
 "`3380 <https://wg21.link/LWG3380>`__","``common_type``\  and comparison categories","Prague","|Complete|","15.0","|spaceship|"
 "`3381 <https://wg21.link/LWG3381>`__","``begin``\  and ``data``\  must agree for ``contiguous_range``\ ","Prague","|Nothing To Do|","","|ranges|"

diff  --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv
index d03017b19c874..10fde392da460 100644
--- a/libcxx/docs/Status/Cxx20Papers.csv
+++ b/libcxx/docs/Status/Cxx20Papers.csv
@@ -108,7 +108,7 @@
 "`P0784R7 <https://wg21.link/P0784R7>`__","CWG","More constexpr containers","Cologne","|Complete|","12.0"
 "`P0980R1 <https://wg21.link/P0980R1>`__","LWG","Making std::string constexpr","Cologne","|Complete|","15.0"
 "`P1004R2 <https://wg21.link/P1004R2>`__","LWG","Making std::vector constexpr","Cologne","|Complete|","15.0"
-"`P1035R7 <https://wg21.link/P1035R7>`__","LWG","Input Range Adaptors (mostly implemented, TODO: elements_view)","Cologne","|In Progress|","","|ranges|"
+"`P1035R7 <https://wg21.link/P1035R7>`__","LWG","Input Range Adaptors","Cologne","|Complete|","16.0","|ranges|"
 "`P1065R2 <https://wg21.link/P1065R2>`__","LWG","Constexpr INVOKE","Cologne","|Complete|","12.0"
 "`P1135R6 <https://wg21.link/P1135R6>`__","LWG","The C++20 Synchronization Library","Cologne","|Complete|","11.0"
 "`P1207R4 <https://wg21.link/P1207R4>`__","LWG","Movability of Single-pass Iterators","Cologne","|Complete|","15.0","|ranges|"
@@ -182,7 +182,7 @@
 "`P1981R0 <https://wg21.link/P1981R0>`__","LWG","Rename leap to leap_second","Prague","* *",""
 "`P1982R0 <https://wg21.link/P1982R0>`__","LWG","Rename link to time_zone_link","Prague","* *",""
 "`P1983R0 <https://wg21.link/P1983R0>`__","LWG","Wording for GB301, US296, US292, US291, and US283","Prague","|Complete|","15.0","|ranges|"
-"`P1994R1 <https://wg21.link/P1994R1>`__","LWG","elements_view needs its own sentinel","Prague","* *","","|ranges|"
+"`P1994R1 <https://wg21.link/P1994R1>`__","LWG","elements_view needs its own sentinel","Prague","Complete","16.0","|ranges|"
 "`P2002R1 <https://wg21.link/P2002R1>`__","CWG","Defaulted comparison specification cleanups","Prague","* *",""
 "`P2045R1 <https://wg21.link/P2045R1>`__","LWG","Missing Mandates for the standard library","Prague","* *",""
 "`P2085R0 <https://wg21.link/P2085R0>`__","CWG","Consistent defaulted comparisons","Prague","* *",""

diff  --git a/libcxx/docs/Status/Cxx2bIssues.csv b/libcxx/docs/Status/Cxx2bIssues.csv
index 2acfc800baf36..e92cfc3b60c42 100644
--- a/libcxx/docs/Status/Cxx2bIssues.csv
+++ b/libcxx/docs/Status/Cxx2bIssues.csv
@@ -22,7 +22,7 @@
 "`3403 <https://wg21.link/LWG3403>`__","Domain of ``ranges::ssize(E)`` doesn't ``match ranges::size(E)``","November 2020","","","|ranges|"
 "`3404 <https://wg21.link/LWG3404>`__","Finish removing subrange's conversions from pair-like","November 2020","","","|ranges|"
 "`3405 <https://wg21.link/LWG3405>`__","``common_view``'s converting constructor is bad, too","November 2020","|Complete|","14.0","|ranges|"
-"`3406 <https://wg21.link/LWG3406>`__","``elements_view::begin()`` and ``elements_view::end()`` have incompatible constraints","November 2020","","","|ranges|"
+"`3406 <https://wg21.link/LWG3406>`__","``elements_view::begin()`` and ``elements_view::end()`` have incompatible constraints","November 2020","|Complete|","16.0","|ranges|"
 "`3419 <https://wg21.link/LWG3419>`__","[algorithms.requirements]/15 doesn't reserve as many rights as it intends to","November 2020","|Nothing To Do|",""
 "`3420 <https://wg21.link/LWG3420>`__","cpp17-iterator should check that the type looks like an iterator first","November 2020","|Complete|","14.0","|ranges|"
 "`3421 <https://wg21.link/LWG3421>`__","Imperfect ADL emulation for boolean-testable","November 2020","|Nothing To Do|","","|ranges|"
@@ -53,11 +53,11 @@
 "`3391 <https://wg21.link/LWG3391>`__","Problems with ``counted_iterator``/``move_iterator::base() const &``","February 2021","","","|ranges|"
 "`3433 <https://wg21.link/LWG3433>`__","``subrange::advance(n)`` has UB when ``n < 0``","February 2021","|Complete|","14.0","|ranges|"
 "`3490 <https://wg21.link/LWG3490>`__","``ranges::drop_while_view::begin()`` is missing a precondition","February 2021","|Nothing To Do|","","|ranges|"
-"`3492 <https://wg21.link/LWG3492>`__","Minimal improvements to ``elements_view::iterator``","February 2021","","","|ranges|"
+"`3492 <https://wg21.link/LWG3492>`__","Minimal improvements to ``elements_view::iterator``","February 2021","|Complete|","16.0","|ranges|"
 "`3494 <https://wg21.link/LWG3494>`__","Allow ranges to be conditionally borrowed","February 2021","Superseded by `P2017R1 <https://wg21.link/P2017R1>`__","","|ranges|"
 "`3495 <https://wg21.link/LWG3495>`__","``constexpr launder`` makes pointers to inactive members of unions usable","February 2021","|Nothing To Do|",""
 "`3500 <https://wg21.link/LWG3500>`__","``join_view::iterator::operator->()`` is bogus","February 2021","|Complete|","14.0","|ranges|"
-"`3502 <https://wg21.link/LWG3502>`__","``elements_view`` should not be allowed to return dangling reference","February 2021","","","|ranges|"
+"`3502 <https://wg21.link/LWG3502>`__","``elements_view`` should not be allowed to return dangling reference","February 2021","|Complete|","16.0","|ranges|"
 "`3505 <https://wg21.link/LWG3505>`__","``split_view::outer-iterator::operator++`` misspecified","February 2021","","","|ranges|"
 "","","","","",""
 `2774 <https://wg21.link/LWG2774>`__,"``std::function`` construction vs assignment","June 2021","",""

diff  --git a/libcxx/docs/Status/SpaceshipProjects.csv b/libcxx/docs/Status/SpaceshipProjects.csv
index dec2a89f9a108..f732ec722115b 100644
--- a/libcxx/docs/Status/SpaceshipProjects.csv
+++ b/libcxx/docs/Status/SpaceshipProjects.csv
@@ -51,7 +51,7 @@ Section,Description,Dependencies,Assignee,Complete
 | `[counted.iter.cmp] <https://wg21.link/counted.iter.cmp>`_,| counted_iterator,None,Unassigned,|Not Started|
 | `[range.iota.iterator] <https://wg21.link/range.iota.iterator>`_,| `ranges::iota_view::iterator <https://reviews.llvm.org/D110774>`_,[concepts.cmp],Arthur O'Dwyer,|Complete|
 | `[range.transform.iterator] <https://wg21.link/range.transform.iterator>`_,| `ranges::transform_view::iterator <https://reviews.llvm.org/D110774>`_,[concepts.cmp],Arthur O'Dwyer,|Complete|
-| `[range.elements.iterator] <https://wg21.link/range.elements.iterator>`_,| ranges::elements_view::iterator,[concepts.cmp],Unassigned,|Not Started|
+| `[range.elements.iterator] <https://wg21.link/range.elements.iterator>`_,| ranges::elements_view::iterator,[concepts.cmp],Hui Xie,|Complete|
 | `[time.duration.comparisons] <https://wg21.link/time.duration.comparisons>`_, "chrono::duration", None, Mark de Wever, |Not Started|
 | `[time.point.comparisons] <https://wg21.link/time.point.comparisons>`_, "chrono::time_point", None, Mark de Wever, |Not Started|
 "| `[time.cal.day.nonmembers] <https://wg21.link/time.cal.day.nonmembers>`_

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 06c5eb16da1c6..ea52f205d4980 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -364,6 +364,7 @@ set(files
   __fwd/span.h
   __fwd/string.h
   __fwd/string_view.h
+  __fwd/subrange.h
   __fwd/tuple.h
   __hash_table
   __ios/fpos.h
@@ -503,6 +504,7 @@ set(files
   __ranges/data.h
   __ranges/drop_view.h
   __ranges/drop_while_view.h
+  __ranges/elements_view.h
   __ranges/empty.h
   __ranges/empty_view.h
   __ranges/enable_borrowed_range.h
@@ -554,10 +556,12 @@ set(files
   __tree
   __tuple_dir/apply_cv.h
   __tuple_dir/make_tuple_types.h
+  __tuple_dir/pair_like.h
   __tuple_dir/sfinae_helpers.h
   __tuple_dir/tuple_element.h
   __tuple_dir/tuple_indices.h
   __tuple_dir/tuple_like.h
+  __tuple_dir/tuple_like_ext.h
   __tuple_dir/tuple_size.h
   __tuple_dir/tuple_types.h
   __type_traits/add_const.h

diff  --git a/libcxx/include/__fwd/get.h b/libcxx/include/__fwd/get.h
index 98758ebca0df2..ec1fab4602d7f 100644
--- a/libcxx/include/__fwd/get.h
+++ b/libcxx/include/__fwd/get.h
@@ -9,9 +9,11 @@
 #ifndef _LIBCPP___FWD_GET_H
 #define _LIBCPP___FWD_GET_H
 
+#include <__concepts/copyable.h>
 #include <__config>
 #include <__fwd/array.h>
 #include <__fwd/pair.h>
+#include <__fwd/subrange.h>
 #include <__fwd/tuple.h>
 #include <__tuple_dir/tuple_element.h>
 #include <cstddef>
@@ -90,6 +92,24 @@ const _Tp&&
 get(const array<_Tp, _Size>&&) _NOEXCEPT;
 #endif
 
+#if _LIBCPP_STD_VER >= 20
+
+namespace ranges {
+
+template <size_t _Index, class _Iter, class _Sent, subrange_kind _Kind>
+  requires((_Index == 0 && copyable<_Iter>) || _Index == 1)
+_LIBCPP_HIDE_FROM_ABI constexpr auto get(const subrange<_Iter, _Sent, _Kind>& __subrange);
+
+template <size_t _Index, class _Iter, class _Sent, subrange_kind _Kind>
+  requires(_Index < 2)
+_LIBCPP_HIDE_FROM_ABI constexpr auto get(subrange<_Iter, _Sent, _Kind>&& __subrange);
+
+} // namespace ranges
+
+using ranges::get;
+
+#endif // _LIBCPP_STD_VER >= 20
+
 _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP___FWD_GET_H

diff  --git a/libcxx/include/__fwd/subrange.h b/libcxx/include/__fwd/subrange.h
new file mode 100644
index 0000000000000..8f7239247eff1
--- /dev/null
+++ b/libcxx/include/__fwd/subrange.h
@@ -0,0 +1,38 @@
+//===---------------------------------------------------------------------===//
+//
+// 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___FWD_SUBRANGE_H
+#define _LIBCPP___FWD_SUBRANGE_H
+
+#include <__config>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+#if _LIBCPP_STD_VER >= 20
+
+#include <__iterator/concepts.h>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+namespace ranges {
+
+enum class _LIBCPP_ENUM_VIS subrange_kind : bool { unsized, sized };
+
+template <input_or_output_iterator _Iter, sentinel_for<_Iter> _Sent, subrange_kind _Kind>
+  requires(_Kind == subrange_kind::sized || !sized_sentinel_for<_Sent, _Iter>)
+class _LIBCPP_TEMPLATE_VIS subrange;
+
+} // namespace ranges
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP_STD_VER >= 20
+
+#endif // _LIBCPP___FWD_SUBRANGE_H

diff  --git a/libcxx/include/__ranges/elements_view.h b/libcxx/include/__ranges/elements_view.h
new file mode 100644
index 0000000000000..3afd6ddbe8f2d
--- /dev/null
+++ b/libcxx/include/__ranges/elements_view.h
@@ -0,0 +1,423 @@
+// -*- 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_ELEMENTS_VIEW_H
+#define _LIBCPP___RANGES_ELEMENTS_VIEW_H
+
+#include <__compare/three_way_comparable.h>
+#include <__concepts/constructible.h>
+#include <__concepts/convertible_to.h>
+#include <__concepts/derived_from.h>
+#include <__concepts/equality_comparable.h>
+#include <__config>
+#include <__fwd/get.h>
+#include <__iterator/concepts.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 <__tuple_dir/tuple_element.h>
+#include <__tuple_dir/tuple_like.h>
+#include <__tuple_dir/tuple_size.h>
+#include <__type_traits/is_reference.h>
+#include <__type_traits/maybe_const.h>
+#include <__type_traits/remove_cv.h>
+#include <__type_traits/remove_cvref.h>
+#include <__type_traits/remove_reference.h>
+#include <__utility/declval.h>
+#include <__utility/forward.h>
+#include <__utility/move.h>
+#include <cstddef>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 20
+
+namespace ranges {
+
+template <class _View, size_t _Np, bool _Const>
+class __elements_view_iterator;
+
+template <class _View, size_t _Np, bool _Const>
+class __elements_view_sentinel;
+
+template <class _Tp, size_t _Np>
+concept __has_tuple_element = __tuple_like<_Tp> && _Np < tuple_size<_Tp>::value;
+
+template <class _Tp, size_t _Np>
+concept __returnable_element = is_reference_v<_Tp> || move_constructible<tuple_element_t<_Np, _Tp>>;
+
+template <input_range _View, size_t _Np>
+  requires view<_View> && __has_tuple_element<range_value_t<_View>, _Np> &&
+           __has_tuple_element<remove_reference_t<range_reference_t<_View>>, _Np> &&
+           __returnable_element<range_reference_t<_View>, _Np>
+class elements_view : public view_interface<elements_view<_View, _Np>> {
+public:
+  _LIBCPP_HIDE_FROM_ABI elements_view()
+    requires default_initializable<_View>
+  = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit elements_view(_View __base) : __base_(std::move(__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_); }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto begin()
+    requires(!__simple_view<_View>)
+  {
+    return __iterator</*_Const=*/false>(ranges::begin(__base_));
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto begin() const
+    requires range<const _View>
+  {
+    return __iterator</*_Const=*/true>(ranges::begin(__base_));
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto end()
+    requires(!__simple_view<_View> && !common_range<_View>)
+  {
+    return __sentinel</*_Const=*/false>{ranges::end(__base_)};
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto end()
+    requires(!__simple_view<_View> && common_range<_View>)
+  {
+    return __iterator</*_Const=*/false>{ranges::end(__base_)};
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto end() const
+    requires range<const _View>
+  {
+    return __sentinel</*_Const=*/true>{ranges::end(__base_)};
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto end() const
+    requires common_range<const _View>
+  {
+    return __iterator</*_Const=*/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_);
+  }
+
+private:
+  template <bool _Const>
+  using __iterator = __elements_view_iterator<_View, _Np, _Const>;
+
+  template <bool _Const>
+  using __sentinel = __elements_view_sentinel<_View, _Np, _Const>;
+
+  _LIBCPP_NO_UNIQUE_ADDRESS _View __base_ = _View();
+};
+
+template <class, size_t>
+struct __elements_view_iterator_category_base {};
+
+template <forward_range _Base, size_t _Np>
+struct __elements_view_iterator_category_base<_Base, _Np> {
+  static consteval auto __get_iterator_category() {
+    using _Result = decltype(std::get<_Np>(*std::declval<iterator_t<_Base>>()));
+    using _Cat    = typename iterator_traits<iterator_t<_Base>>::iterator_category;
+
+    if constexpr (!is_lvalue_reference_v<_Result>) {
+      return input_iterator_tag{};
+    } else if constexpr (derived_from<_Cat, random_access_iterator_tag>) {
+      return random_access_iterator_tag{};
+    } else {
+      return _Cat{};
+    }
+  }
+
+  using iterator_category = decltype(__get_iterator_category());
+};
+
+template <class _View, size_t _Np, bool _Const>
+class __elements_view_iterator : public __elements_view_iterator_category_base<__maybe_const<_Const, _View>, _Np> {
+  template <class, size_t, bool >
+  friend class __elements_view_iterator;
+
+  template <class, size_t, bool >
+  friend class __elements_view_sentinel;
+
+  using _Base = __maybe_const<_Const, _View>;
+
+  iterator_t<_Base> __current_ = iterator_t<_Base>();
+
+  _LIBCPP_HIDE_FROM_ABI static constexpr decltype(auto) __get_element(const iterator_t<_Base>& __i) {
+    if constexpr (is_reference_v<range_reference_t<_Base>>) {
+      return std::get<_Np>(*__i);
+    } else {
+      using _Element = remove_cv_t<tuple_element_t<_Np, range_reference_t<_Base>>>;
+      return static_cast<_Element>(std::get<_Np>(*__i));
+    }
+  }
+
+  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{};
+    }
+  }
+
+public:
+  using iterator_concept = decltype(__get_iterator_concept());
+  using value_type       = remove_cvref_t<tuple_element_t<_Np, range_value_t<_Base>>>;
+  using 
diff erence_type  = range_
diff erence_t<_Base>;
+
+  _LIBCPP_HIDE_FROM_ABI __elements_view_iterator()
+    requires default_initializable<iterator_t<_Base>>
+  = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit __elements_view_iterator(iterator_t<_Base> __current)
+      : __current_(std::move(__current)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __elements_view_iterator(__elements_view_iterator<_View, _Np, !_Const> __i)
+    requires _Const && convertible_to<iterator_t<_View>, iterator_t<_Base>>
+      : __current_(std::move(__i.__current_)) {}
+
+  _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 decltype(auto) operator*() const { return __get_element(__current_); }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __elements_view_iterator& operator++() {
+    ++__current_;
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr void operator++(int) { ++__current_; }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __elements_view_iterator operator++(int)
+    requires forward_range<_Base>
+  {
+    auto temp = *this;
+    ++__current_;
+    return temp;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __elements_view_iterator& operator--()
+    requires bidirectional_range<_Base>
+  {
+    --__current_;
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __elements_view_iterator operator--(int)
+    requires bidirectional_range<_Base>
+  {
+    auto temp = *this;
+    --__current_;
+    return temp;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __elements_view_iterator& operator+=(
diff erence_type __n)
+    requires random_access_range<_Base>
+  {
+    __current_ += __n;
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __elements_view_iterator& operator-=(
diff erence_type __n)
+    requires random_access_range<_Base>
+  {
+    __current_ -= __n;
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr decltype(auto) operator[](
diff erence_type __n) const
+    requires random_access_range<_Base>
+  {
+    return __get_element(__current_ + __n);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator==(const __elements_view_iterator& __x, const __elements_view_iterator& __y)
+    requires equality_comparable<iterator_t<_Base>>
+  {
+    return __x.__current_ == __y.__current_;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator<(const __elements_view_iterator& __x, const __elements_view_iterator& __y)
+    requires random_access_range<_Base>
+  {
+    return __x.__current_ < __y.__current_;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator>(const __elements_view_iterator& __x, const __elements_view_iterator& __y)
+    requires random_access_range<_Base>
+  {
+    return __y < __x;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator<=(const __elements_view_iterator& __x, const __elements_view_iterator& __y)
+    requires random_access_range<_Base>
+  {
+    return !(__y < __x);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator>=(const __elements_view_iterator& __x, const __elements_view_iterator& __y)
+    requires random_access_range<_Base>
+  {
+    return !(__x < __y);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr auto
+  operator<=>(const __elements_view_iterator& __x, const __elements_view_iterator& __y)
+    requires random_access_range<_Base> && three_way_comparable<iterator_t<_Base>>
+  {
+    return __x.__current_ <=> __y.__current_;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr __elements_view_iterator
+  operator+(const __elements_view_iterator& __x, 
diff erence_type __y)
+    requires random_access_range<_Base>
+  {
+    return __elements_view_iterator{__x} += __y;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr __elements_view_iterator
+  operator+(
diff erence_type __x, const __elements_view_iterator& __y)
+    requires random_access_range<_Base>
+  {
+    return __y + __x;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr __elements_view_iterator
+  operator-(const __elements_view_iterator& __x, 
diff erence_type __y)
+    requires random_access_range<_Base>
+  {
+    return __elements_view_iterator{__x} -= __y;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr 
diff erence_type
+  operator-(const __elements_view_iterator& __x, const __elements_view_iterator& __y)
+    requires sized_sentinel_for<iterator_t<_Base>, iterator_t<_Base>>
+  {
+    return __x.__current_ - __y.__current_;
+  }
+};
+
+template <class _View, size_t _Np, bool _Const>
+class __elements_view_sentinel {
+private:
+  using _Base                                        = __maybe_const<_Const, _View>;
+  _LIBCPP_NO_UNIQUE_ADDRESS sentinel_t<_Base> __end_ = sentinel_t<_Base>();
+
+  template <class, size_t, bool >
+  friend class __elements_view_sentinel;
+
+  template <bool _AnyConst>
+  _LIBCPP_HIDE_FROM_ABI static constexpr decltype(auto)
+  __get_current(const __elements_view_iterator<_View, _Np, _AnyConst>& __iter) {
+    return (__iter.__current_);
+  }
+
+public:
+  _LIBCPP_HIDE_FROM_ABI __elements_view_sentinel() = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit __elements_view_sentinel(sentinel_t<_Base> __end)
+      : __end_(std::move(__end)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __elements_view_sentinel(__elements_view_sentinel<_View, _Np, !_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 __elements_view_iterator<_View, _Np, _OtherConst>& __x, const __elements_view_sentinel& __y) {
+    return __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_
diff erence_t<__maybe_const<_OtherConst, _View>>
+  operator-(const __elements_view_iterator<_View, _Np, _OtherConst>& __x, const __elements_view_sentinel& __y) {
+    return __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_
diff erence_t<__maybe_const<_OtherConst, _View>>
+  operator-(const __elements_view_sentinel& __x, const __elements_view_iterator<_View, _Np, _OtherConst>& __y) {
+    return __x.__end_ - __get_current(__y);
+  }
+};
+
+template <class _Tp, size_t _Np>
+inline constexpr bool enable_borrowed_range<elements_view<_Tp, _Np>> = enable_borrowed_range<_Tp>;
+
+template <class _Tp>
+using keys_view = elements_view<_Tp, 0>;
+template <class _Tp>
+using values_view = elements_view<_Tp, 1>;
+
+namespace views {
+namespace __elements {
+
+template <size_t _Np>
+struct __fn : __range_adaptor_closure<__fn<_Np>> {
+  template <class _Range>
+  [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Range&& __range) const
+      /**/ noexcept(noexcept(elements_view<all_t<_Range&&>, _Np>(std::forward<_Range>(__range))))
+      /*------*/ -> decltype(elements_view<all_t<_Range&&>, _Np>(std::forward<_Range>(__range))) {
+    /*-------------*/ return elements_view<all_t<_Range&&>, _Np>(std::forward<_Range>(__range));
+  }
+};
+} // namespace __elements
+
+inline namespace __cpo {
+template <size_t _Np>
+inline constexpr auto elements = __elements::__fn<_Np>{};
+inline constexpr auto keys     = elements<0>;
+inline constexpr auto values   = elements<1>;
+} // namespace __cpo
+} // namespace views
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 20
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___RANGES_ELEMENTS_VIEW_H

diff  --git a/libcxx/include/__ranges/subrange.h b/libcxx/include/__ranges/subrange.h
index f47db6b843f0a..2d9e4cc7e55e2 100644
--- a/libcxx/include/__ranges/subrange.h
+++ b/libcxx/include/__ranges/subrange.h
@@ -18,6 +18,7 @@
 #include <__concepts/
diff erent_from.h>
 #include <__config>
 #include <__fwd/get.h>
+#include <__fwd/subrange.h>
 #include <__iterator/advance.h>
 #include <__iterator/concepts.h>
 #include <__iterator/incrementable_traits.h>
@@ -28,6 +29,7 @@
 #include <__ranges/enable_borrowed_range.h>
 #include <__ranges/size.h>
 #include <__ranges/view_interface.h>
+#include <__tuple_dir/pair_like.h>
 #include <__tuple_dir/tuple_element.h>
 #include <__tuple_dir/tuple_size.h>
 #include <__type_traits/conditional.h>
@@ -59,17 +61,6 @@ namespace ranges {
     convertible_to<_From, _To> &&
     !__uses_nonqualification_pointer_conversion<decay_t<_From>, decay_t<_To>>;
 
-  template<class _Tp>
-  concept __pair_like =
-    !is_reference_v<_Tp> && requires(_Tp __t) {
-      typename tuple_size<_Tp>::type; // Ensures `tuple_size<T>` is complete.
-      requires derived_from<tuple_size<_Tp>, integral_constant<size_t, 2>>;
-      typename tuple_element_t<0, remove_const_t<_Tp>>;
-      typename tuple_element_t<1, remove_const_t<_Tp>>;
-      { std::get<0>(__t) } -> convertible_to<const tuple_element_t<0, _Tp>&>;
-      { std::get<1>(__t) } -> convertible_to<const tuple_element_t<1, _Tp>&>;
-    };
-
   template<class _Pair, class _Iter, class _Sent>
   concept __pair_like_convertible_from =
     !range<_Pair> && __pair_like<_Pair> &&
@@ -77,8 +68,6 @@ namespace ranges {
     __convertible_to_non_slicing<_Iter, tuple_element_t<0, _Pair>> &&
     convertible_to<_Sent, tuple_element_t<1, _Pair>>;
 
-  enum class _LIBCPP_ENUM_VIS subrange_kind : bool { unsized, sized };
-
   template<input_or_output_iterator _Iter, sentinel_for<_Iter> _Sent = _Iter,
            subrange_kind _Kind = sized_sentinel_for<_Sent, _Iter>
              ? subrange_kind::sized

diff  --git a/libcxx/include/__tuple_dir/pair_like.h b/libcxx/include/__tuple_dir/pair_like.h
new file mode 100644
index 0000000000000..87407ad95b6ff
--- /dev/null
+++ b/libcxx/include/__tuple_dir/pair_like.h
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// 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___TUPLE_PAIR_LIKE_H
+#define _LIBCPP___TUPLE_PAIR_LIKE_H
+
+#include <__config>
+#include <__tuple_dir/tuple_like.h>
+#include <__tuple_dir/tuple_size.h>
+#include <__type_traits/remove_cvref.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 20
+
+template <class _Tp>
+concept __pair_like = __tuple_like<_Tp> && tuple_size<remove_cvref_t<_Tp>>::value == 2;
+
+#endif // _LIBCPP_STD_VER >= 20
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___TUPLE_PAIR_LIKE_H

diff  --git a/libcxx/include/__tuple_dir/sfinae_helpers.h b/libcxx/include/__tuple_dir/sfinae_helpers.h
index fde53419d07b9..fcd65a0817be2 100644
--- a/libcxx/include/__tuple_dir/sfinae_helpers.h
+++ b/libcxx/include/__tuple_dir/sfinae_helpers.h
@@ -13,7 +13,7 @@
 #include <__fwd/tuple.h>
 #include <__tuple_dir/make_tuple_types.h>
 #include <__tuple_dir/tuple_element.h>
-#include <__tuple_dir/tuple_like.h>
+#include <__tuple_dir/tuple_like_ext.h>
 #include <__tuple_dir/tuple_size.h>
 #include <__tuple_dir/tuple_types.h>
 #include <__type_traits/enable_if.h>
@@ -58,8 +58,8 @@ struct __tuple_sfinae_base {
 
 // __tuple_convertible
 
-template <class _Tp, class _Up, bool = __tuple_like<__libcpp_remove_reference_t<_Tp> >::value,
-                                bool = __tuple_like<_Up>::value>
+template <class _Tp, class _Up, bool = __tuple_like_ext<__libcpp_remove_reference_t<_Tp> >::value,
+                                bool = __tuple_like_ext<_Up>::value>
 struct __tuple_convertible
     : public false_type {};
 
@@ -73,8 +73,8 @@ struct __tuple_convertible<_Tp, _Up, true, true>
 
 // __tuple_constructible
 
-template <class _Tp, class _Up, bool = __tuple_like<__libcpp_remove_reference_t<_Tp> >::value,
-                                bool = __tuple_like<_Up>::value>
+template <class _Tp, class _Up, bool = __tuple_like_ext<__libcpp_remove_reference_t<_Tp> >::value,
+                                bool = __tuple_like_ext<_Up>::value>
 struct __tuple_constructible
     : public false_type {};
 
@@ -88,8 +88,8 @@ struct __tuple_constructible<_Tp, _Up, true, true>
 
 // __tuple_assignable
 
-template <class _Tp, class _Up, bool = __tuple_like<__libcpp_remove_reference_t<_Tp> >::value,
-                                bool = __tuple_like<_Up>::value>
+template <class _Tp, class _Up, bool = __tuple_like_ext<__libcpp_remove_reference_t<_Tp> >::value,
+                                bool = __tuple_like_ext<_Up>::value>
 struct __tuple_assignable
     : public false_type {};
 
@@ -117,7 +117,7 @@ struct __tuple_like_with_size_imp<true, _SizeTrait, _Expected>
 
 template <class _Tuple, size_t _ExpectedSize, class _RawTuple = __libcpp_remove_reference_t<_Tuple> >
 using __tuple_like_with_size _LIBCPP_NODEBUG = __tuple_like_with_size_imp<
-                                   __tuple_like<_RawTuple>::value,
+                                   __tuple_like_ext<_RawTuple>::value,
                                    tuple_size<_RawTuple>, _ExpectedSize
                               >;
 

diff  --git a/libcxx/include/__tuple_dir/tuple_like.h b/libcxx/include/__tuple_dir/tuple_like.h
index 327287786e3b2..dab395be616b7 100644
--- a/libcxx/include/__tuple_dir/tuple_like.h
+++ b/libcxx/include/__tuple_dir/tuple_like.h
@@ -12,9 +12,10 @@
 #include <__config>
 #include <__fwd/array.h>
 #include <__fwd/pair.h>
+#include <__fwd/subrange.h>
 #include <__fwd/tuple.h>
-#include <__tuple_dir/tuple_types.h>
 #include <__type_traits/integral_constant.h>
+#include <__type_traits/remove_cvref.h>
 #include <cstddef>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -23,21 +24,27 @@
 
 _LIBCPP_BEGIN_NAMESPACE_STD
 
-template <class _Tp> struct __tuple_like : false_type {};
+#if _LIBCPP_STD_VER >= 20
 
-template <class _Tp> struct __tuple_like<const _Tp> : public __tuple_like<_Tp> {};
-template <class _Tp> struct __tuple_like<volatile _Tp> : public __tuple_like<_Tp> {};
-template <class _Tp> struct __tuple_like<const volatile _Tp> : public __tuple_like<_Tp> {};
+template <class _Tp>
+struct __tuple_like_impl : false_type {};
 
-#ifndef _LIBCPP_CXX03_LANG
-template <class... _Tp> struct __tuple_like<tuple<_Tp...> > : true_type {};
-#endif
+template <class... _Tp>
+struct __tuple_like_impl<tuple<_Tp...> > : true_type {};
+
+template <class _T1, class _T2>
+struct __tuple_like_impl<pair<_T1, _T2> > : true_type {};
+
+template <class _Tp, size_t _Size>
+struct __tuple_like_impl<array<_Tp, _Size> > : true_type {};
 
-template <class _T1, class _T2> struct __tuple_like<pair<_T1, _T2> > : true_type {};
+template <class _Ip, class _Sp, ranges::subrange_kind _Kp>
+struct __tuple_like_impl<ranges::subrange<_Ip, _Sp, _Kp> > : true_type {};
 
-template <class _Tp, size_t _Size> struct __tuple_like<array<_Tp, _Size> > : true_type {};
+template <class _Tp>
+concept __tuple_like = __tuple_like_impl<remove_cvref_t<_Tp>>::value;
 
-template <class... _Tp> struct __tuple_like<__tuple_types<_Tp...> > : true_type {};
+#endif // _LIBCPP_STD_VER >= 20
 
 _LIBCPP_END_NAMESPACE_STD
 

diff  --git a/libcxx/include/__tuple_dir/tuple_like_ext.h b/libcxx/include/__tuple_dir/tuple_like_ext.h
new file mode 100644
index 0000000000000..bf9869611273f
--- /dev/null
+++ b/libcxx/include/__tuple_dir/tuple_like_ext.h
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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___TUPLE_TUPLE_LIKE_EXT_H
+#define _LIBCPP___TUPLE_TUPLE_LIKE_EXT_H
+
+#include <__config>
+#include <__fwd/array.h>
+#include <__fwd/pair.h>
+#include <__fwd/tuple.h>
+#include <__tuple_dir/tuple_types.h>
+#include <__type_traits/integral_constant.h>
+#include <cstddef>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+template <class _Tp> struct __tuple_like_ext : false_type {};
+
+template <class _Tp> struct __tuple_like_ext<const _Tp> : public __tuple_like_ext<_Tp> {};
+template <class _Tp> struct __tuple_like_ext<volatile _Tp> : public __tuple_like_ext<_Tp> {};
+template <class _Tp> struct __tuple_like_ext<const volatile _Tp> : public __tuple_like_ext<_Tp> {};
+
+#ifndef _LIBCPP_CXX03_LANG
+template <class... _Tp> struct __tuple_like_ext<tuple<_Tp...> > : true_type {};
+#endif
+
+template <class _T1, class _T2> struct __tuple_like_ext<pair<_T1, _T2> > : true_type {};
+
+template <class _Tp, size_t _Size> struct __tuple_like_ext<array<_Tp, _Size> > : true_type {};
+
+template <class... _Tp> struct __tuple_like_ext<__tuple_types<_Tp...> > : true_type {};
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___TUPLE_TUPLE_LIKE_EXT_H

diff  --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index d0cb522cc89ae..5d4cf53aa334e 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1229,6 +1229,7 @@ module std [system] {
       module data                   { private header "__ranges/data.h" }
       module drop_view              { private header "__ranges/drop_view.h" }
       module drop_while_view        { private header "__ranges/drop_while_view.h" }
+      module elements_view          { private header "__ranges/elements_view.h" }
       module empty                  { private header "__ranges/empty.h" }
       module empty_view             { private header "__ranges/empty_view.h" }
       module enable_borrowed_range  { private header "__ranges/enable_borrowed_range.h" }
@@ -1250,7 +1251,11 @@ module std [system] {
       module reverse_view           { private header "__ranges/reverse_view.h" }
       module single_view            { private header "__ranges/single_view.h" }
       module size                   { private header "__ranges/size.h" }
-      module subrange               { private header "__ranges/subrange.h" }
+      module subrange               {
+        private header "__ranges/subrange.h"
+
+        module subrange_fwd { private header "__fwd/subrange.h" }
+      }
       module take_view              { private header "__ranges/take_view.h" }
       module take_while_view        { private header "__ranges/take_while_view.h" }
       module transform_view         {
@@ -1365,11 +1370,13 @@ module std [system] {
     module apply_cv         { private header "__tuple_dir/apply_cv.h" }
     module get_fwd          { private header "__fwd/get.h" }
     module make_tuple_types { private header "__tuple_dir/make_tuple_types.h" }
+    module pair_like        { private header "__tuple_dir/pair_like.h" }
     module sfinae_helpers   { private header "__tuple_dir/sfinae_helpers.h" }
     module tuple_element    { private header "__tuple_dir/tuple_element.h" }
     module tuple_fwd        { private header "__fwd/tuple.h" }
     module tuple_indices    { private header "__tuple_dir/tuple_indices.h" }
     module tuple_like       { private header "__tuple_dir/tuple_like.h" }
+    module tuple_like_ext   { private header "__tuple_dir/tuple_like_ext.h" }
     module tuple_size       { private header "__tuple_dir/tuple_size.h" }
     module tuple_types      { private header "__tuple_dir/tuple_types.h" }
   }

diff  --git a/libcxx/include/ranges b/libcxx/include/ranges
index 5928efa6027ec..db601d48c7f10 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -115,6 +115,27 @@ namespace std::ranges {
   template<range R>
     using borrowed_subrange_t = see below;
 
+  // [range.elements], elements view
+  template<input_range V, size_t N>
+    requires see below
+  class elements_view;
+
+  template<class T, size_t N>
+    inline constexpr bool enable_borrowed_range<elements_view<T, N>> =
+      enable_borrowed_range<T>;
+
+  template<class R>
+    using keys_view = elements_view<R, 0>;
+  template<class R>
+    using values_view = elements_view<R, 1>;
+
+  namespace views {
+    template<size_t N>
+      inline constexpr unspecified elements = unspecified;
+    inline constexpr auto keys = elements<0>;
+    inline constexpr auto values = elements<1>;
+  }
+
   // [range.empty], empty view
   template<class T>
     requires is_object_v<T>
@@ -316,6 +337,7 @@ namespace std {
 #include <__ranges/data.h>
 #include <__ranges/drop_view.h>
 #include <__ranges/drop_while_view.h>
+#include <__ranges/elements_view.h>
 #include <__ranges/empty.h>
 #include <__ranges/empty_view.h>
 #include <__ranges/enable_borrowed_range.h>

diff  --git a/libcxx/include/tuple b/libcxx/include/tuple
index b75d00c4e431b..b0616bd94da53 100644
--- a/libcxx/include/tuple
+++ b/libcxx/include/tuple
@@ -1677,7 +1677,7 @@ struct __tuple_cat_return_1<tuple<_Types...>, true, _Tuple0, _Tuple1, _Tuples...
                      tuple<_Types...>,
                      typename __make_tuple_types<__remove_cvref_t<_Tuple0> >::type
                  >::type,
-                 __tuple_like<__libcpp_remove_reference_t<_Tuple1> >::value,
+                 __tuple_like_ext<__libcpp_remove_reference_t<_Tuple1> >::value,
                  _Tuple1, _Tuples...>
 {
 };
@@ -1687,7 +1687,7 @@ template <class ..._Tuples> struct __tuple_cat_return;
 template <class _Tuple0, class ..._Tuples>
 struct __tuple_cat_return<_Tuple0, _Tuples...>
     : public __tuple_cat_return_1<tuple<>,
-         __tuple_like<__libcpp_remove_reference_t<_Tuple0> >::value, _Tuple0,
+         __tuple_like_ext<__libcpp_remove_reference_t<_Tuple0> >::value, _Tuple0,
                                                                      _Tuples...>
 {
 };

diff  --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp
index 97e945ef8fde8..f88ee37062d46 100644
--- a/libcxx/test/libcxx/private_headers.verify.cpp
+++ b/libcxx/test/libcxx/private_headers.verify.cpp
@@ -396,6 +396,7 @@ END-SCRIPT
 #include <__fwd/span.h> // expected-error@*:* {{use of private header from outside its module: '__fwd/span.h'}}
 #include <__fwd/string.h> // expected-error@*:* {{use of private header from outside its module: '__fwd/string.h'}}
 #include <__fwd/string_view.h> // expected-error@*:* {{use of private header from outside its module: '__fwd/string_view.h'}}
+#include <__fwd/subrange.h> // expected-error@*:* {{use of private header from outside its module: '__fwd/subrange.h'}}
 #include <__fwd/tuple.h> // expected-error@*:* {{use of private header from outside its module: '__fwd/tuple.h'}}
 #include <__ios/fpos.h> // expected-error@*:* {{use of private header from outside its module: '__ios/fpos.h'}}
 #include <__iterator/access.h> // expected-error@*:* {{use of private header from outside its module: '__iterator/access.h'}}
@@ -534,6 +535,7 @@ END-SCRIPT
 #include <__ranges/data.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/data.h'}}
 #include <__ranges/drop_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/drop_view.h'}}
 #include <__ranges/drop_while_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/drop_while_view.h'}}
+#include <__ranges/elements_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/elements_view.h'}}
 #include <__ranges/empty.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/empty.h'}}
 #include <__ranges/empty_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/empty_view.h'}}
 #include <__ranges/enable_borrowed_range.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/enable_borrowed_range.h'}}
@@ -567,10 +569,12 @@ END-SCRIPT
 #include <__thread/timed_backoff_policy.h> // expected-error@*:* {{use of private header from outside its module: '__thread/timed_backoff_policy.h'}}
 #include <__tuple_dir/apply_cv.h> // expected-error@*:* {{use of private header from outside its module: '__tuple_dir/apply_cv.h'}}
 #include <__tuple_dir/make_tuple_types.h> // expected-error@*:* {{use of private header from outside its module: '__tuple_dir/make_tuple_types.h'}}
+#include <__tuple_dir/pair_like.h> // expected-error@*:* {{use of private header from outside its module: '__tuple_dir/pair_like.h'}}
 #include <__tuple_dir/sfinae_helpers.h> // expected-error@*:* {{use of private header from outside its module: '__tuple_dir/sfinae_helpers.h'}}
 #include <__tuple_dir/tuple_element.h> // expected-error@*:* {{use of private header from outside its module: '__tuple_dir/tuple_element.h'}}
 #include <__tuple_dir/tuple_indices.h> // expected-error@*:* {{use of private header from outside its module: '__tuple_dir/tuple_indices.h'}}
 #include <__tuple_dir/tuple_like.h> // expected-error@*:* {{use of private header from outside its module: '__tuple_dir/tuple_like.h'}}
+#include <__tuple_dir/tuple_like_ext.h> // expected-error@*:* {{use of private header from outside its module: '__tuple_dir/tuple_like_ext.h'}}
 #include <__tuple_dir/tuple_size.h> // expected-error@*:* {{use of private header from outside its module: '__tuple_dir/tuple_size.h'}}
 #include <__tuple_dir/tuple_types.h> // expected-error@*:* {{use of private header from outside its module: '__tuple_dir/tuple_types.h'}}
 #include <__type_traits/add_const.h> // expected-error@*:* {{use of private header from outside its module: '__type_traits/add_const.h'}}

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.elements/elements_view.no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.elements/elements_view.no_unique_address.compile.pass.cpp
new file mode 100644
index 0000000000000..6901745b6be29
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.elements/elements_view.no_unique_address.compile.pass.cpp
@@ -0,0 +1,33 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// clang-cl and cl currently don't support [[no_unique_address]]
+// XFAIL: msvc
+
+// Test the libc++ extension that the base view stored in `std::ranges::elements_view`
+// has been marked as _LIBCPP_NO_UNIQUE_ADDRESS
+
+#include <ranges>
+#include <tuple>
+
+
+struct EmptyView : std::ranges::view_base {
+  std::tuple<int>* begin() const;
+  std::tuple<int>* end() const;
+};
+
+using ElementsView = std::ranges::elements_view<EmptyView, 0>;
+
+struct TestClass {
+  [[no_unique_address]] ElementsView view;
+  int i;
+};
+
+static_assert(sizeof(TestClass) == sizeof(int));

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.elements/sentinel.no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.elements/sentinel.no_unique_address.compile.pass.cpp
new file mode 100644
index 0000000000000..2e69a327a7625
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.elements/sentinel.no_unique_address.compile.pass.cpp
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// clang-cl and cl currently don't support [[no_unique_address]]
+// XFAIL: msvc
+
+// Test the libc++ extension that the sentinel stored in `std::ranges::elements_view::__sentinel`
+// has been marked as _LIBCPP_NO_UNIQUE_ADDRESS
+
+#include <ranges>
+#include <tuple>
+
+struct EmptySentinel {
+  friend bool operator==(std::tuple<int>* iter, EmptySentinel) { return iter; }
+};
+
+struct Range : std::ranges::view_base {
+  std::tuple<int>* begin() const;
+  EmptySentinel end() const;
+};
+
+using ElementsView = std::ranges::elements_view<Range, 0>;
+using ElementsSent = std::ranges::sentinel_t<ElementsView>;
+
+struct TestClass {
+  [[no_unique_address]] ElementsSent s;
+  int i;
+};
+
+static_assert(sizeof(TestClass) == sizeof(int));

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/adaptor.pass.cpp
new file mode 100644
index 0000000000000..d68d6e57e2ed5
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/adaptor.pass.cpp
@@ -0,0 +1,182 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// std::views::elements<N>
+// std::views::keys
+// std::views::values
+
+#include <algorithm>
+#include <cassert>
+#include <ranges>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+template <class T>
+struct View : std::ranges::view_base {
+  T* begin() const;
+  T* end() const;
+};
+
+static_assert(!std::is_invocable_v<decltype((std::views::elements<0>))>);
+static_assert(!std::is_invocable_v<decltype((std::views::elements<0>)), View<int>>);
+static_assert(std::is_invocable_v<decltype((std::views::elements<0>)), View<std::pair<int, int>>>);
+static_assert(std::is_invocable_v<decltype((std::views::elements<0>)), View<std::tuple<int>>>);
+static_assert(!std::is_invocable_v<decltype((std::views::elements<5>)), View<std::tuple<int>>>);
+
+static_assert(!std::is_invocable_v<decltype((std::views::keys))>);
+static_assert(!std::is_invocable_v<decltype((std::views::keys)), View<int>>);
+static_assert(std::is_invocable_v<decltype((std::views::keys)), View<std::pair<int, int>>>);
+static_assert(std::is_invocable_v<decltype((std::views::keys)), View<std::tuple<int>>>);
+
+static_assert(!std::is_invocable_v<decltype((std::views::values))>);
+static_assert(!std::is_invocable_v<decltype((std::views::values)), View<int>>);
+static_assert(std::is_invocable_v<decltype((std::views::values)), View<std::pair<int, int>>>);
+static_assert(!std::is_invocable_v<decltype((std::views::values)), View<std::tuple<int>>>);
+
+template <class View, class T>
+concept CanBePiped =
+    requires(View&& view, T&& t) {
+      { std::forward<View>(view) | std::forward<T>(t) };
+    };
+
+static_assert(!CanBePiped<View<int>, decltype((std::views::elements<0>))>);
+static_assert(CanBePiped<View<std::pair<int, int>>, decltype((std::views::elements<0>))>);
+static_assert(CanBePiped<View<std::tuple<int>>, decltype((std::views::elements<0>))>);
+static_assert(!CanBePiped<View<std::tuple<int>>, decltype((std::views::elements<5>))>);
+
+static_assert(!CanBePiped<View<int>, decltype((std::views::keys))>);
+static_assert(CanBePiped<View<std::pair<int, int>>, decltype((std::views::keys))>);
+static_assert(CanBePiped<View<std::tuple<int>>, decltype((std::views::keys))>);
+
+static_assert(!CanBePiped<View<int>, decltype((std::views::values))>);
+static_assert(CanBePiped<View<std::pair<int, int>>, decltype((std::views::values))>);
+static_assert(!CanBePiped<View<std::tuple<int>>, decltype((std::views::values))>);
+
+constexpr bool test() {
+  std::pair<int, int> buff[] = {{1, 2}, {3, 4}, {5, 6}};
+
+  // Test `views::elements<N>(v)`
+  {
+    using Result = std::ranges::elements_view<std::ranges::ref_view<std::pair<int, int>[3]>, 0>;
+    std::same_as<Result> decltype(auto) result = std::views::elements<0>(buff);
+    auto expected                              = {1, 3, 5};
+    assert(std::ranges::equal(result, expected));
+  }
+
+  // Test `views::keys(v)`
+  {
+    using Result = std::ranges::elements_view<std::ranges::ref_view<std::pair<int, int>[3]>, 0>;
+    std::same_as<Result> decltype(auto) result = std::views::keys(buff);
+    auto expected                              = {1, 3, 5};
+    assert(std::ranges::equal(result, expected));
+  }
+
+  // Test `views::values(v)`
+  {
+    using Result = std::ranges::elements_view<std::ranges::ref_view<std::pair<int, int>[3]>, 1>;
+    std::same_as<Result> decltype(auto) result = std::views::values(buff);
+    auto expected                              = {2, 4, 6};
+    assert(std::ranges::equal(result, expected));
+  }
+
+  // Test `v | views::elements<N>`
+  {
+    using Result = std::ranges::elements_view<std::ranges::ref_view<std::pair<int, int>[3]>, 1>;
+    std::same_as<Result> decltype(auto) result = buff | std::views::elements<1>;
+    auto expected                              = {2, 4, 6};
+    assert(std::ranges::equal(result, expected));
+  }
+
+  // Test `v | views::keys`
+  {
+    using Result = std::ranges::elements_view<std::ranges::ref_view<std::pair<int, int>[3]>, 0>;
+    std::same_as<Result> decltype(auto) result = buff | std::views::keys;
+    auto expected                              = {1, 3, 5};
+    assert(std::ranges::equal(result, expected));
+  }
+
+  // Test `v | views::values`
+  {
+    using Result = std::ranges::elements_view<std::ranges::ref_view<std::pair<int, int>[3]>, 1>;
+    std::same_as<Result> decltype(auto) result = buff | std::views::values;
+    auto expected                              = {2, 4, 6};
+    assert(std::ranges::equal(result, expected));
+  }
+
+  // Test views::elements<0> | views::elements<0>
+  {
+    std::pair<std::tuple<int>, std::tuple<int>> nested[] = {{{1}, {2}}, {{3}, {4}}, {{5}, {6}}};
+    using Result                                         = std::ranges::elements_view<
+        std::ranges::elements_view<std::ranges::ref_view<std::pair<std::tuple<int>, std::tuple<int>>[3]>, 0>,
+        0>;
+    auto const partial                         = std::views::elements<0> | std::views::elements<0>;
+    std::same_as<Result> decltype(auto) result = nested | partial;
+    auto expected                              = {1, 3, 5};
+    assert(std::ranges::equal(result, expected));
+  }
+
+  // Test views::keys | views::keys
+  {
+    std::pair<std::tuple<int>, std::tuple<int>> nested[] = {{{1}, {2}}, {{3}, {4}}, {{5}, {6}}};
+    using Result                                         = std::ranges::elements_view<
+        std::ranges::elements_view<std::ranges::ref_view<std::pair<std::tuple<int>, std::tuple<int>>[3]>, 0>,
+        0>;
+    auto const partial                         = std::views::keys | std::views::keys;
+    std::same_as<Result> decltype(auto) result = nested | partial;
+    auto expected                              = {1, 3, 5};
+    assert(std::ranges::equal(result, expected));
+  }
+
+  // Test views::values | views::values
+  {
+    std::pair<std::tuple<int>, std::tuple<int, int>> nested[] = {{{1}, {2, 3}}, {{4}, {5, 6}}, {{7}, {8, 9}}};
+    using Result                                              = std::ranges::elements_view<
+        std::ranges::elements_view<std::ranges::ref_view<std::pair<std::tuple<int>, std::tuple<int, int>>[3]>, 1>,
+        1>;
+    auto const partial                         = std::views::values | std::views::values;
+    std::same_as<Result> decltype(auto) result = nested | partial;
+    auto expected                              = {3, 6, 9};
+    assert(std::ranges::equal(result, expected));
+  }
+
+  // Test views::keys | views::values
+  {
+    std::pair<std::tuple<int, int>, std::tuple<int>> nested[] = {{{1, 2}, {3}}, {{4, 5}, {6}}, {{7, 8}, {9}}};
+    using Result                                              = std::ranges::elements_view<
+        std::ranges::elements_view<std::ranges::ref_view<std::pair<std::tuple<int, int>, std::tuple<int>>[3]>, 0>,
+        1>;
+    auto const partial                         = std::views::keys | std::views::values;
+    std::same_as<Result> decltype(auto) result = nested | partial;
+    auto expected                              = {2, 5, 8};
+    assert(std::ranges::equal(result, expected));
+  }
+
+  // Test views::values | views::keys
+  {
+    std::pair<std::tuple<int>, std::tuple<int, int>> nested[] = {{{1}, {2, 3}}, {{4}, {5, 6}}, {{7}, {8, 9}}};
+    using Result                                              = std::ranges::elements_view<
+        std::ranges::elements_view<std::ranges::ref_view<std::pair<std::tuple<int>, std::tuple<int, int>>[3]>, 1>,
+        0>;
+    auto const partial                         = std::views::values | std::views::keys;
+    std::same_as<Result> decltype(auto) result = nested | partial;
+    auto expected                              = {2, 5, 8};
+    assert(std::ranges::equal(result, expected));
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/base.pass.cpp
new file mode 100644
index 0000000000000..7fbd27d58190e
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/base.pass.cpp
@@ -0,0 +1,84 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr V base() const & requires copy_constructible<V> { return base_; }
+// constexpr V base() && { return std::move(base_); }
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include "MoveOnly.h"
+
+struct View : std::ranges::view_base {
+  int i;
+  std::tuple<int>* begin() const;
+  std::tuple<int>* end() const;
+};
+
+struct MoveOnlyView : View {
+  MoveOnly mo;
+};
+
+template <class T>
+concept HasBase = requires(T&& t) { std::forward<T>(t).base(); };
+
+static_assert(HasBase<std::ranges::elements_view<View, 0> const&>);
+static_assert(HasBase<std::ranges::elements_view<View, 0>&&>);
+
+static_assert(!HasBase<std::ranges::elements_view<MoveOnlyView, 0> const&>);
+static_assert(HasBase<std::ranges::elements_view<MoveOnlyView, 0>&&>);
+
+constexpr bool test() {
+  // const &
+  {
+    const std::ranges::elements_view<View, 0> ev{View{{}, 5}};
+    std::same_as<View> decltype(auto) v = ev.base();
+    assert(v.i == 5);
+  }
+
+  // &
+  {
+    std::ranges::elements_view<View, 0> ev{View{{}, 5}};
+    std::same_as<View> decltype(auto) v = ev.base();
+    assert(v.i == 5);
+  }
+
+  // &&
+  {
+    std::ranges::elements_view<View, 0> ev{View{{}, 5}};
+    std::same_as<View> decltype(auto) v = std::move(ev).base();
+    assert(v.i == 5);
+  }
+
+  // const &&
+  {
+    const std::ranges::elements_view<View, 0> ev{View{{}, 5}};
+    std::same_as<View> decltype(auto) v = std::move(ev).base();
+    assert(v.i == 5);
+  }
+
+  // move only
+  {
+    std::ranges::elements_view<MoveOnlyView, 0> ev{MoveOnlyView{{}, 5}};
+    std::same_as<MoveOnlyView> decltype(auto) v = std::move(ev).base();
+    assert(v.mo.get() == 5);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/begin.pass.cpp
new file mode 100644
index 0000000000000..824e8f8ed4873
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/begin.pass.cpp
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+//  constexpr auto begin() requires (!simple-view<V>)
+//  constexpr auto begin() const requires range<const V>
+
+#include <cassert>
+#include <concepts>
+#include <ranges>
+#include <tuple>
+#include <utility>
+
+#include "types.h"
+
+template <class T>
+concept HasConstBegin = requires(const T ct) { ct.begin(); };
+
+template <class T>
+concept HasBegin = requires(T t) { t.begin(); };
+
+template <class T>
+concept HasConstAndNonConstBegin =
+    HasConstBegin<T> &&
+    // because const begin and non-const begin returns 
diff erent types (iterator<true>, iterator<false>)
+    requires(T t, const T ct) { requires !std::same_as<decltype(t.begin()), decltype(ct.begin())>; };
+
+template <class T>
+concept HasOnlyNonConstBegin = HasBegin<T> && !HasConstBegin<T>;
+
+template <class T>
+concept HasOnlyConstBegin = HasConstBegin<T> && !HasConstAndNonConstBegin<T>;
+
+struct NoConstBeginView : TupleBufferView {
+  using TupleBufferView::TupleBufferView;
+  constexpr std::tuple<int>* begin() { return buffer_; }
+  constexpr std::tuple<int>* end() { return buffer_ + size_; }
+};
+
+// simple-view<V>
+static_assert(HasOnlyConstBegin<std::ranges::elements_view<SimpleCommon, 0>>);
+
+// !simple-view<V> && range<const V>
+static_assert(HasConstAndNonConstBegin<std::ranges::elements_view<NonSimpleCommon, 0>>);
+
+// !range<const V>
+static_assert(HasOnlyNonConstBegin<std::ranges::elements_view<NoConstBeginView, 0>>);
+
+constexpr bool test() {
+  std::tuple<int> buffer[] = {{1}, {2}};
+  {
+    // underlying iterator should be pointing to the first element
+    auto ev   = std::views::elements<0>(buffer);
+    auto iter = ev.begin();
+    assert(&(*iter) == &std::get<0>(buffer[0]));
+  }
+
+  {
+    // underlying range models simple-view
+    auto v = std::views::elements<0>(SimpleCommon{buffer});
+    static_assert(std::is_same_v<decltype(v.begin()), decltype(std::as_const(v).begin())>);
+    assert(v.begin() == std::as_const(v).begin());
+    auto&& r = *std::as_const(v).begin();
+    assert(&r == &std::get<0>(buffer[0]));
+  }
+
+  {
+    // underlying const R is not a range
+    auto v   = std::views::elements<0>(NoConstBeginView{buffer});
+    auto&& r = *v.begin();
+    assert(&r == &std::get<0>(buffer[0]));
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/borrowed.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/borrowed.compile.pass.cpp
new file mode 100644
index 0000000000000..8cc08e355d4f9
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/borrowed.compile.pass.cpp
@@ -0,0 +1,32 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+// template<class T, size_t N>
+//   inline constexpr bool enable_borrowed_range<elements_view<T, N>> =
+//     enable_borrowed_range<T>;
+
+#include <ranges>
+#include <tuple>
+
+struct NonBorrowed : std::ranges::view_base {
+  std::tuple<int>* begin();
+  std::tuple<int>* end();
+};
+
+struct Borrowed : std::ranges::view_base {
+  std::tuple<int>* begin();
+  std::tuple<int>* end();
+};
+
+template <>
+inline constexpr bool std::ranges::enable_borrowed_range<Borrowed> = true;
+
+static_assert(!std::ranges::borrowed_range<std::ranges::elements_view<NonBorrowed, 0>>);
+static_assert(std::ranges::borrowed_range<std::ranges::elements_view<Borrowed, 0>>);

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..44a566d765ee4
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/ctor.default.pass.cpp
@@ -0,0 +1,47 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// elements_view() requires default_initializable<V> = default;
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+#include <type_traits>
+
+template <bool DefaultInitializable>
+struct View : std::ranges::view_base {
+  int i = 42;
+  constexpr explicit View()
+    requires DefaultInitializable
+  = default;
+  std::tuple<int>* begin() const;
+  std::tuple<int>* end() const;
+};
+
+
+// clang-format off
+static_assert( std::is_default_constructible_v<std::ranges::elements_view<View<true >, 0>>);
+static_assert(!std::is_default_constructible_v<std::ranges::elements_view<View<false>, 0>>);
+// clang-format on
+
+constexpr bool test() {
+  {
+    std::ranges::elements_view<View<true>, 0> ev = {};
+    assert(ev.base().i == 42);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/ctor.view.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/ctor.view.pass.cpp
new file mode 100644
index 0000000000000..eda9266b1289c
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/ctor.view.pass.cpp
@@ -0,0 +1,44 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr explicit elements_view(V base);
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include "MoveOnly.h"
+
+struct View : std::ranges::view_base {
+  MoveOnly mo;
+  std::tuple<int>* begin() const;
+  std::tuple<int>* end() const;
+};
+
+// Test Explicit
+static_assert(std::is_constructible_v<std::ranges::elements_view<View, 0>, View>);
+static_assert(!std::is_convertible_v<View, std::ranges::elements_view<View, 0>>);
+
+constexpr bool test() {
+  {
+    std::ranges::elements_view<View, 0> ev{View{{}, MoveOnly{5}}};
+    assert(std::move(ev).base().mo.get() == 5);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/end.pass.cpp
new file mode 100644
index 0000000000000..003ce50381f77
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/end.pass.cpp
@@ -0,0 +1,142 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr auto end() requires (!simple-view<V> && !common_range<V>)
+// constexpr auto end() requires (!simple-view<V> && common_range<V>)
+// constexpr auto end() const requires range<const V>
+// constexpr auto end() const requires common_range<const V>
+
+#include <cassert>
+#include <iterator>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+
+#include "types.h"
+
+// | simple | common |      v.end()     | as_const(v)
+// |        |        |                  |   .end()
+// |--------|--------|------------------|---------------
+// |   Y    |   Y    |  iterator<true>  | iterator<true>
+// |   Y    |   N    |  sentinel<true>  | sentinel<true>
+// |   N    |   Y    |  iterator<false> | iterator<true>
+// |   N    |   N    |  sentinel<false> | sentinel<true>
+
+// !range<const V>
+template <class T>
+concept HasEnd = requires(T t) { t.end(); };
+
+template <class T>
+concept HasConstEnd = requires(const T ct) { ct.end(); };
+
+struct NoConstEndView : TupleBufferView {
+  using TupleBufferView::TupleBufferView;
+  constexpr std::tuple<int>* begin() { return buffer_; }
+  constexpr std::tuple<int>* end() { return buffer_ + size_; }
+};
+
+static_assert(HasEnd<std::ranges::elements_view<NoConstEndView, 0>>);
+static_assert(!HasConstEnd<std::ranges::elements_view<NoConstEndView, 0>>);
+
+constexpr bool test() {
+  std::tuple<int> buffer[] = {{1}, {2}, {3}};
+
+  // simple-view && common_view
+  {
+    SimpleCommon v{buffer};
+    auto ev = std::views::elements<0>(v);
+
+    auto it           = ev.begin();
+    decltype(auto) st = ev.end();
+    assert(st == it + 3);
+
+    auto const_it           = std::as_const(ev).begin();
+    decltype(auto) const_st = std::as_const(ev).end();
+    assert(const_st == const_it + 3);
+
+    // Both iterator<true>
+    static_assert(std::same_as<decltype(st), decltype(const_st)>);
+    static_assert(std::same_as<decltype(st), decltype(it)>);
+    static_assert(std::same_as<decltype(const_st), decltype(const_it)>);
+  }
+
+  // simple-view && !common_view
+  {
+    SimpleNonCommon v{buffer};
+    auto ev = std::views::elements<0>(v);
+
+    auto it           = ev.begin();
+    decltype(auto) st = ev.end();
+    assert(st == it + 3);
+
+    auto const_it           = std::as_const(ev).begin();
+    decltype(auto) const_st = std::as_const(ev).end();
+    assert(const_st == const_it + 3);
+
+    // Both iterator<true>
+    static_assert(std::same_as<decltype(st), decltype(const_st)>);
+    static_assert(!std::same_as<decltype(st), decltype(it)>);
+    static_assert(!std::same_as<decltype(const_st), decltype(const_it)>);
+  }
+
+  // !simple-view && common_view
+  {
+    NonSimpleCommon v{buffer};
+    auto ev = std::views::elements<0>(v);
+
+    auto it           = ev.begin();
+    decltype(auto) st = ev.end();
+    assert(st == it + 3);
+
+    auto const_it           = std::as_const(ev).begin();
+    decltype(auto) const_st = std::as_const(ev).end();
+    assert(const_st == const_it + 3);
+
+    // iterator<false> and iterator<true>
+    static_assert(!std::same_as<decltype(st), decltype(const_st)>);
+    static_assert(std::same_as<decltype(st), decltype(it)>);
+    static_assert(std::same_as<decltype(const_st), decltype(const_it)>);
+  }
+
+  // !simple-view && !common_view
+  {
+    NonSimpleNonCommon v{buffer};
+    auto ev = std::views::elements<0>(v);
+
+    auto it           = ev.begin();
+    decltype(auto) st = ev.end();
+    assert(st == it + 3);
+
+    auto const_it           = std::as_const(ev).begin();
+    decltype(auto) const_st = std::as_const(ev).end();
+    assert(const_st == const_it + 3);
+
+    // sentinel<false> and sentinel<true>
+    static_assert(!std::same_as<decltype(st), decltype(const_st)>);
+    static_assert(!std::same_as<decltype(st), decltype(it)>);
+    static_assert(!std::same_as<decltype(const_st), decltype(const_it)>);
+  }
+
+  // LWG 3406 elements_view::begin() and elements_view::end() have incompatible constraints
+  {
+    std::tuple<int, int> x[] = {{0, 0}};
+    std::ranges::subrange r  = {std::counted_iterator(x, 1), std::default_sentinel};
+    auto v                   = r | std::views::elements<0>;
+    assert(v.begin() != v.end());
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/general.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/general.pass.cpp
new file mode 100644
index 0000000000000..78792ae54bdbf
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/general.pass.cpp
@@ -0,0 +1,103 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// Some basic examples of how elements_view might be used in the wild. This is a general
+// collection of sample algorithms and functions that try to mock general usage of
+// this view.
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <map>
+#include <ranges>
+#include <string_view>
+#include <tuple>
+#include <utility>
+
+#include "test_iterators.h"
+
+int main(int, char**) {
+  using namespace std::string_view_literals;
+  auto historicalFigures =
+      std::map{std::pair{"Lovelace"sv, 1815}, {"Turing"sv, 1912}, {"Babbage"sv, 1791}, {"Hamilton"sv, 1936}};
+  auto expectedYears = {1791, 1936, 1815, 1912};
+
+  // views::elements<N>
+  {
+    auto names         = historicalFigures | std::views::elements<0>;
+    auto expectedNames = {"Babbage"sv, "Hamilton"sv, "Lovelace"sv, "Turing"sv};
+    assert(std::ranges::equal(names, expectedNames));
+
+    auto birth_years = historicalFigures | std::views::elements<1>;
+    assert(std::ranges::equal(birth_years, expectedYears));
+  }
+
+  // views::keys
+  {
+    auto names         = historicalFigures | std::views::keys;
+    auto expectedNames = {"Babbage"sv, "Hamilton"sv, "Lovelace"sv, "Turing"sv};
+    assert(std::ranges::equal(names, expectedNames));
+  }
+
+  // views::values
+  {
+    auto is_even = [](const auto x) { return x % 2 == 0; };
+    assert(std::ranges::count_if(historicalFigures | std::views::values, is_even) == 2);
+  }
+
+  // array
+  {
+    std::array<int, 3> arrs[] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
+    auto ev                   = arrs | std::views::elements<2>;
+    auto expected             = {3, 6, 9};
+    assert(std::ranges::equal(ev, expected));
+  }
+
+  // pair
+  {
+    std::pair<double, int> ps[] = {{1.0, 2}, {3.0, 4}, {5.0, 6}};
+    auto ev                     = ps | std::views::elements<1>;
+    auto expected               = {2, 4, 6};
+    assert(std::ranges::equal(ev, expected));
+  }
+
+  // tuple
+  {
+    std::tuple<short> tps[] = {{1}, {2}, {3}};
+    auto ev                 = tps | std::views::elements<0>;
+    auto expected           = {1, 2, 3};
+    assert(std::ranges::equal(ev, expected));
+  }
+
+  // subrange
+  {
+    int is[]       = {1, 2, 3, 4, 5, 6, 7, 8, 9};
+    using Iter     = forward_iterator<int*>;
+    using Sent     = sentinel_wrapper<Iter>;
+    using SubRange = std::ranges::subrange<Iter, Sent>;
+    SubRange sr[]  = {
+        {Iter{is}, Sent{Iter{is + 1}}},
+        {Iter{is + 2}, Sent{Iter{is + 5}}},
+        {Iter{is + 6}, Sent{Iter{is + 8}}},
+    };
+
+    auto iters = sr | std::views::elements<0>;
+    static_assert(std::is_same_v<Iter, std::ranges::range_reference_t<decltype(iters)>>);
+    auto expectedIters = {is, is + 2, is + 6};
+    assert(std::ranges::equal(iters | std::views::transform([](auto&& iter) { return base(iter); }), expectedIters));
+
+    auto sentinels = sr | std::views::elements<1>;
+    static_assert(std::is_same_v<Sent, std::ranges::range_reference_t<decltype(sentinels)>>);
+    auto expectedSentinels = {is + 1, is + 5, is + 8};
+    assert(std::ranges::equal(
+        sentinels | std::views::transform([](auto&& st) { return base(base(st)); }), expectedSentinels));
+  }
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/arithmetic.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/arithmetic.pass.cpp
new file mode 100644
index 0000000000000..7a4ce83f1a9f6
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/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
+
+// constexpr iterator& operator+=(
diff erence_type n)
+//    requires random_access_range<Base>;
+//
+// constexpr iterator& operator-=(
diff erence_type n)
+//   requires random_access_range<Base>;
+//
+// friend constexpr iterator operator+(const iterator& x, 
diff erence_type y)
+//     requires random_access_range<Base>;
+//
+// friend constexpr iterator operator+(
diff erence_type x, const iterator& y)
+//   requires random_access_range<Base>;
+//
+// friend constexpr iterator operator-(const iterator& x, 
diff erence_type y)
+//   requires random_access_range<Base>;
+//
+// friend constexpr 
diff erence_type operator-(const iterator& x, const iterator& y)
+//   requires sized_sentinel_for<iterator_t<Base>, iterator_t<Base>>;
+
+#include <ranges>
+
+#include <tuple>
+
+#include "test_iterators.h"
+
+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 ElemIter = std::ranges::iterator_t<std::ranges::elements_view<BaseRange, 0>>;
+
+using RandomAccessRange = std::ranges::subrange<std::tuple<int>*>;
+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<ElemIter<RandomAccessRange>, int>);
+static_assert(CanPlus<int, ElemIter<RandomAccessRange>>);
+static_assert(CanPlusEqual<ElemIter<RandomAccessRange>, int>);
+static_assert(CanMinus<ElemIter<RandomAccessRange>, int>);
+static_assert(CanMinus<ElemIter<RandomAccessRange>, ElemIter<RandomAccessRange>>);
+static_assert(CanMinusEqual<ElemIter<RandomAccessRange>, int>);
+
+using BidiRange = std::ranges::subrange<bidirectional_iterator<std::tuple<int>*>>;
+static_assert(!std::ranges::random_access_range<BidiRange>);
+static_assert(!std::sized_sentinel_for<std::ranges::iterator_t<BidiRange>, //
+                                       std::ranges::iterator_t<BidiRange>>);
+
+static_assert(!CanPlus<ElemIter<BidiRange>, int>);
+static_assert(!CanPlus<int, ElemIter<BidiRange>>);
+static_assert(!CanPlusEqual<ElemIter<BidiRange>, int>);
+static_assert(!CanMinus<ElemIter<BidiRange>, int>);
+static_assert(!CanMinus<ElemIter<BidiRange>, ElemIter<BidiRange>>);
+static_assert(!CanMinusEqual<ElemIter<BidiRange>, int>);
+
+constexpr bool test() {
+  std::tuple<int> ts[] = {{1}, {2}, {3}, {4}};
+
+  RandomAccessRange r{&ts[0], &ts[0] + 4};
+  auto ev = r | std::views::elements<0>;
+  {
+    // 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 - 3;
+    assert(it2.base() == &ts[1]);
+
+    it1 -= 3;
+    assert(it1 == it2);
+    assert(it1.base() == &ts[1]);
+  }
+
+  {
+    // operator-(x, y)
+    assert((ev.end() - ev.begin()) == 4);
+
+    auto it1 = ev.begin() + 2;
+    auto it2 = ev.end() - 1;
+    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.elements/iterator/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/base.pass.cpp
new file mode 100644
index 0000000000000..3729c8e543113
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/base.pass.cpp
@@ -0,0 +1,98 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// constexpr const iterator_t<Base>& base() const & noexcept;
+// constexpr iterator_t<Base> base() &&;
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+#include "MoveOnly.h"
+#include "../types.h"
+
+// Test Noexcept
+template <class T>
+concept IsBaseNoexcept =
+    requires {
+      { std::declval<T>().base() } noexcept;
+    };
+
+using BaseView     = std::ranges::subrange<std::tuple<int>*>;
+using ElementsIter = std::ranges::iterator_t<std::ranges::elements_view<BaseView, 0>>;
+
+static_assert(IsBaseNoexcept<const ElementsIter&>);
+static_assert(IsBaseNoexcept<ElementsIter&>);
+static_assert(IsBaseNoexcept<const ElementsIter&&>);
+static_assert(!IsBaseNoexcept<ElementsIter&&>);
+
+constexpr bool test() {
+  std::tuple<int> t{5};
+
+  // const &
+  {
+    const ElementsIter it{&t};
+    decltype(auto) base = it.base();
+    static_assert(std::is_same_v<decltype(base), std::tuple<int>* const&>);
+    assert(base == &t);
+  }
+
+  // &
+  {
+    ElementsIter it{&t};
+    decltype(auto) base = it.base();
+    static_assert(std::is_same_v<decltype(base), std::tuple<int>* const&>);
+    assert(base == &t);
+  }
+
+  // &&
+  {
+    ElementsIter it{&t};
+    decltype(auto) base = std::move(it).base();
+    static_assert(std::is_same_v<decltype(base), std::tuple<int>*>);
+    assert(base == &t);
+  }
+
+  // const &&
+  {
+    const ElementsIter it{&t};
+    decltype(auto) base = std::move(it).base();
+    static_assert(std::is_same_v<decltype(base), std::tuple<int>* const&>);
+    assert(base == &t);
+  }
+
+  // move only
+  {
+    struct MoveOnlyIter : IterBase<MoveOnlyIter> {
+      MoveOnly mo;
+    };
+    struct Sent {
+      constexpr bool operator==(const MoveOnlyIter&) const { return true; }
+    };
+
+    using MoveOnlyElemIter =
+        std::ranges::iterator_t<std::ranges::elements_view<std::ranges::subrange<MoveOnlyIter, Sent>, 0>>;
+
+    MoveOnlyElemIter it{MoveOnlyIter{{}, MoveOnly{5}}};
+    decltype(auto) base = std::move(it).base();
+    static_assert(std::is_same_v<decltype(base), MoveOnlyIter>);
+    assert(base.mo.get() == 5);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/compare.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/compare.pass.cpp
new file mode 100644
index 0000000000000..16df3e40bd77a
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/compare.pass.cpp
@@ -0,0 +1,155 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// friend constexpr bool operator==(const iterator& x, const iterator& y)
+//   requires equality_comparable<iterator_t<Base>>;
+// friend constexpr bool operator<(const iterator& x, const iterator& y)
+//   requires random_access_range<Base>;
+// friend constexpr bool operator>(const iterator& x, const iterator& y)
+//   requires random_access_range<Base>;
+// friend constexpr bool operator<=(const iterator& x, const iterator& y)
+//   requires random_access_range<Base>;
+// friend constexpr bool operator>=(const iterator& x, const iterator& y)
+//   requires random_access_range<Base>;
+// friend constexpr auto operator<=>(const iterator& x, const iterator& y)
+//   requires random_access_range<Base> && three_way_comparable<iterator_t<Base>>;
+
+#include <compare>
+#include <functional>
+#include <ranges>
+#include <tuple>
+
+#include "test_iterators.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));
+}
+
+constexpr void inequalityOperatorsDoNotExistTest(const auto& iter1, const auto& iter2) {
+  using Iter1 = decltype(iter1);
+  using Iter2 = decltype(iter2);
+  static_assert(!std::is_invocable_v<std::less<>, Iter1, Iter2>);
+  static_assert(!std::is_invocable_v<std::less_equal<>, Iter1, Iter2>);
+  static_assert(!std::is_invocable_v<std::greater<>, Iter1, Iter2>);
+  static_assert(!std::is_invocable_v<std::greater_equal<>, Iter1, Iter2>);
+}
+
+constexpr bool test() {
+  std::tuple<int> ts[] = {{1}, {2}, {3}};
+
+  {
+    // Test a new-school iterator with operator<=>; the iterator should also have operator<=>.
+    using It       = three_way_contiguous_iterator<std::tuple<int>*>;
+    using Subrange = std::ranges::subrange<It>;
+    static_assert(std::three_way_comparable<It>);
+    using R = std::ranges::elements_view<Subrange, 0>;
+    static_assert(std::three_way_comparable<std::ranges::iterator_t<R>>);
+
+    auto ev    = Subrange{It{&ts[0]}, It{&ts[0] + 3}} | std::views::elements<0>;
+    auto iter1 = ev.begin();
+    auto iter2 = iter1 + 1;
+
+    compareOperatorTest(iter1, iter2);
+
+    assert((iter1 <=> iter2) == std::strong_ordering::less);
+    assert((iter1 <=> iter1) == std::strong_ordering::equal);
+    assert((iter2 <=> iter1) == std::strong_ordering::greater);
+  }
+
+  {
+    // Test an old-school iterator with no operator<=>; the elements view iterator shouldn't have
+    // operator<=> either.
+    using It       = random_access_iterator<std::tuple<int>*>;
+    using Subrange = std::ranges::subrange<It>;
+    static_assert(!std::three_way_comparable<It>);
+    using R = std::ranges::elements_view<Subrange, 0>;
+    static_assert(!std::three_way_comparable<std::ranges::iterator_t<R>>);
+
+    auto ev    = Subrange{It{&ts[0]}, It{&ts[0] + 3}} | std::views::elements<0>;
+    auto iter1 = ev.begin();
+    auto iter2 = iter1 + 1;
+
+    compareOperatorTest(iter1, iter2);
+  }
+
+  {
+    // non random_access_range
+    using It       = bidirectional_iterator<std::tuple<int>*>;
+    using Subrange = std::ranges::subrange<It>;
+    static_assert(!std::ranges::random_access_range<Subrange>);
+    using R = std::ranges::elements_view<Subrange, 0>;
+    static_assert(!std::three_way_comparable<std::ranges::iterator_t<R>>);
+
+    auto ev = Subrange{It{&ts[0]}, It{&ts[0] + 1}} | std::views::elements<0>;
+
+    auto it1 = ev.begin();
+    auto it2 = ev.end();
+
+    assert(it1 == it1);
+    assert(!(it1 != it1));
+    assert(it2 == it2);
+    assert(!(it2 != it2));
+
+    assert(it1 != it2);
+
+    ++it1;
+    assert(it1 == it2);
+
+    inequalityOperatorsDoNotExistTest(it1, it2);
+  }
+
+  {
+    // underlying iterator does not support ==
+    using Iter     = cpp20_input_iterator<std::tuple<int>*>;
+    using Sent     = sentinel_wrapper<Iter>;
+    using Subrange = std::ranges::subrange<Iter, Sent>;
+    using R        = std::ranges::elements_view<Subrange, 0>;
+    static_assert(!std::three_way_comparable<std::ranges::iterator_t<R>>);
+
+    auto ev = Subrange{Iter{&ts[0]}, Sent{Iter{&ts[0] + 1}}} | std::views::elements<0>;
+    auto it = ev.begin();
+
+    using ElemIter = decltype(it);
+    static_assert(!std::invocable<std::equal_to<>, ElemIter, ElemIter>);
+    static_assert(!std::invocable<std::not_equal_to<>, ElemIter, ElemIter>);
+    inequalityOperatorsDoNotExistTest(it, it);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.base.pass.cpp
new file mode 100644
index 0000000000000..5a280d294aecc
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.base.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
+
+// constexpr explicit iterator(iterator_t<Base> current);
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+
+#include "../types.h"
+
+// Test explicit
+using BaseIter     = std::tuple<int>*;
+using ElementsIter = std::ranges::iterator_t<std::ranges::elements_view<std::ranges::subrange<BaseIter, BaseIter>, 0>>;
+
+static_assert(std::is_constructible_v<ElementsIter, BaseIter>);
+static_assert(!std::is_convertible_v<BaseIter, ElementsIter>);
+
+struct TracedMoveIter : IterBase<TracedMoveIter>{
+  bool moved = false;
+
+  constexpr TracedMoveIter()                      = default;
+  constexpr TracedMoveIter(const TracedMoveIter&) = default;
+  constexpr TracedMoveIter(TracedMoveIter&&) : moved{true} {}
+  constexpr TracedMoveIter& operator=(TracedMoveIter&&)      = default;
+  constexpr TracedMoveIter& operator=(const TracedMoveIter&) = default;
+};
+
+struct TracedMoveView : std::ranges::view_base {
+  TracedMoveIter begin() const;
+  TracedMoveIter end() const;
+};
+
+constexpr bool test() {
+  using Iter = std::ranges::iterator_t<std::ranges::elements_view<TracedMoveView, 0>>;
+  Iter iter{TracedMoveIter{}};
+  assert(iter.base().moved);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..62aa9b2a1a1f7
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.default.pass.cpp
@@ -0,0 +1,58 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// iterator() requires default_initializable<iterator_t<Base>> = default;
+
+#include <ranges>
+#include <tuple>
+
+#include "../types.h"
+
+struct PODIter : IterBase<PODIter> {
+  int i; // deliberately uninitialised
+};
+
+struct IterDefaultCtrView : std::ranges::view_base {
+  PODIter begin() const;
+  PODIter end() const;
+};
+
+struct IterNoDefaultCtrView : std::ranges::view_base {
+  cpp20_input_iterator<std::tuple<int>*> begin() const;
+  sentinel_wrapper<cpp20_input_iterator<std::tuple<int>*>> end() const;
+};
+
+template <class View, size_t N>
+using ElementsIter = std::ranges::iterator_t<std::ranges::elements_view<View, N>>;
+
+static_assert(!std::default_initializable<ElementsIter<IterNoDefaultCtrView, 0>>);
+static_assert(std::default_initializable<ElementsIter<IterDefaultCtrView, 0>>);
+
+constexpr bool test() {
+  using Iter = ElementsIter<IterDefaultCtrView, 0>;
+  {
+    Iter iter;
+    assert(iter.base().i == 0); // PODIter has to be initialised to have value 0
+  }
+
+  {
+    Iter iter = {};
+    assert(iter.base().i == 0); // PODIter has to be initialised to have value 0
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.other.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.other.pass.cpp
new file mode 100644
index 0000000000000..6756474024821
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/ctor.other.pass.cpp
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr iterator(iterator<!Const> i)
+//   requires Const && convertible_to<iterator_t<V>, iterator_t<Base>>;
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+
+#include "../types.h"
+
+template <bool Const>
+struct ConvertibleIter : IterBase<ConvertibleIter<Const>> {
+  using iterator_category = std::random_access_iterator_tag;
+  using value_type        = std::tuple<int>;
+  using 
diff erence_type   = intptr_t;
+
+  bool movedFromOtherConst = false;
+  int i                    = 0;
+
+  constexpr ConvertibleIter() = default;
+  constexpr ConvertibleIter(int ii) : i(ii) {}
+  template <bool otherConst>
+    requires(Const != otherConst)
+  constexpr ConvertibleIter(ConvertibleIter<otherConst> it) : movedFromOtherConst(true), i(it.i) {}
+};
+
+template <class Iter, class ConstIter>
+struct BasicView : std::ranges::view_base {
+  Iter begin();
+  Iter end();
+
+  ConstIter begin() const;
+  ConstIter end() const;
+};
+
+template <class View>
+using ElemIter = std::ranges::iterator_t<std::ranges::elements_view<View, 0>>;
+
+template <class View>
+using ConstElemIter = std::ranges::iterator_t<const std::ranges::elements_view<View, 0>>;
+
+using ConvertibleView    = BasicView<ConvertibleIter<false>, ConvertibleIter<true>>;
+using NonConvertibleView = BasicView<forward_iterator<std::tuple<int>*>, bidirectional_iterator<std::tuple<int>*>>;
+
+static_assert(std::is_constructible_v<ConstElemIter<ConvertibleView>, ElemIter<ConvertibleView>>);
+static_assert(!std::is_constructible_v<ElemIter<ConvertibleView>, ConstElemIter<ConvertibleView>>);
+static_assert(!std::is_constructible_v<ConstElemIter<NonConvertibleView>, ElemIter<NonConvertibleView>>);
+static_assert(!std::is_constructible_v<ElemIter<NonConvertibleView>, ConstElemIter<NonConvertibleView>>);
+
+constexpr bool test() {
+  ElemIter<ConvertibleView> iter{ConvertibleIter<false>{5}};
+  ConstElemIter<ConvertibleView> constIter = iter; // implicit
+  assert(constIter.base().movedFromOtherConst);
+  assert(constIter.base().i == 5);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/decrement.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/decrement.pass.cpp
new file mode 100644
index 0000000000000..f0bed5e1d0345
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/decrement.pass.cpp
@@ -0,0 +1,91 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr iterator& operator--() requires bidirectional_range<Base>;
+// constexpr iterator operator--(int) requires bidirectional_range<Base>;
+
+#include <array>
+#include <cassert>
+#include <ranges>
+#include <tuple>
+
+#include "test_iterators.h"
+
+template <class Iter>
+concept CanPreDecrement = requires(Iter it) { --it; };
+
+template <class Iter>
+concept CanPostDecrement = requires(Iter it) { it--; };
+
+template <class Iter, class Sent = sentinel_wrapper<Iter>>
+constexpr void testOne() {
+  using Range          = std::ranges::subrange<Iter, Sent>;
+  std::tuple<int> ts[] = {{1}, {2}, {3}};
+  auto ev              = Range{Iter{&ts[0]}, Sent{Iter{&ts[0] + 3}}} | std::views::elements<0>;
+
+  using ElementIter = std::ranges::iterator_t<decltype(ev)>;
+
+  if constexpr (!std::bidirectional_iterator<Iter>) {
+    auto it = ev.begin();
+    static_assert(!CanPreDecrement<decltype(it)>);
+    static_assert(!CanPostDecrement<decltype(it)>);
+  } else {
+    // --i
+    {
+      auto it = ev.begin();
+      static_assert(CanPreDecrement<decltype(it)>);
+
+      ++it;
+      assert(base(it.base()) == &ts[1]);
+
+      decltype(auto) result = --it;
+
+      static_assert(std::is_same_v<decltype(result), ElementIter&>);
+      assert(&result == &it);
+
+      assert(base(it.base()) == &ts[0]);
+    }
+
+    // i--
+    {
+      auto it = ev.begin();
+      static_assert(CanPostDecrement<decltype(it)>);
+
+      ++it;
+      assert(base(it.base()) == &ts[1]);
+
+      decltype(auto) result = it--;
+
+      static_assert(std::is_same_v<decltype(result), ElementIter>);
+
+      assert(base(it.base()) == &ts[0]);
+      assert(base(result.base()) == &ts[1]);
+    }
+  }
+}
+
+constexpr bool test() {
+  using Ptr = std::tuple<int>*;
+  testOne<cpp20_input_iterator<Ptr>>();
+  testOne<forward_iterator<Ptr>>();
+  testOne<bidirectional_iterator<Ptr>>();
+  testOne<random_access_iterator<Ptr>>();
+  testOne<contiguous_iterator<Ptr>>();
+  testOne<Ptr>();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/deref.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/deref.pass.cpp
new file mode 100644
index 0000000000000..d87a3e5339203
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/deref.pass.cpp
@@ -0,0 +1,100 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// constexpr decltype(auto) operator*() const;
+
+#include <array>
+#include <cassert>
+#include <ranges>
+#include <tuple>
+#include <utility>
+
+template <std::size_t N, class T, std::size_t Size>
+constexpr void testReference(T (&ts)[Size]) {
+  auto ev = ts | std::views::elements<N>;
+  auto it = ev.begin();
+
+  decltype(auto) result = *it;
+  using ExpectedType    = decltype(std::get<N>(ts[0]));
+  static_assert(std::is_same_v<decltype(result), ExpectedType>);
+
+  if constexpr (std::is_reference_v<ExpectedType>) {
+    // tuple/array/pair
+    assert(&result == &std::get<N>(ts[0]));
+  } else {
+    // subrange
+    assert(result == std::get<N>(ts[0]));
+  }
+}
+
+// LWG 3502 elements_view should not be allowed to return dangling references
+template <std::size_t N, class T>
+constexpr void testValue(T t) {
+  auto ev = std::views::iota(0, 1) | std::views::transform([&t](int) { return t; }) | std::views::elements<N>;
+  auto it = ev.begin();
+
+  decltype(auto) result = *it;
+  using ExpectedType    = std::remove_cvref_t<decltype(std::get<N>(t))>;
+  static_assert(std::is_same_v<decltype(result), ExpectedType>);
+
+  assert(result == std::get<N>(t));
+}
+
+constexpr bool test() {
+  // test tuple
+  {
+    std::tuple<int, short, long> ts[] = {{1, 2, 3}, {4, 5, 6}};
+    testReference<0>(ts);
+    testReference<1>(ts);
+    testReference<2>(ts);
+    testValue<0>(ts[0]);
+    testValue<1>(ts[0]);
+    testValue<2>(ts[0]);
+  }
+
+  // test pair
+  {
+    std::pair<int, short> ps[] = {{1, 2}, {4, 5}};
+    testReference<0>(ps);
+    testReference<1>(ps);
+    testValue<0>(ps[0]);
+    testValue<1>(ps[0]);
+  }
+
+  // test array
+  {
+    std::array<int, 3> arrs[] = {{1, 2, 3}, {3, 4, 5}};
+    testReference<0>(arrs);
+    testReference<1>(arrs);
+    testReference<2>(arrs);
+    testValue<0>(arrs[0]);
+    testValue<1>(arrs[0]);
+    testValue<2>(arrs[0]);
+  }
+
+  // test subrange
+  {
+    int i                             = 5;
+    std::ranges::subrange<int*> srs[] = {{&i, &i}, {&i, &i}};
+    testReference<0>(srs);
+    testReference<1>(srs);
+    testValue<0>(srs[0]);
+    testValue<1>(srs[0]);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/increment.pass.cpp
new file mode 100644
index 0000000000000..6b29ba0239e56
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/increment.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
+
+// constexpr iterator& operator++();
+// constexpr void operator++(int);
+// constexpr iterator operator++(int) requires forward_range<Base>;
+
+#include <array>
+#include <cassert>
+#include <ranges>
+#include <tuple>
+
+#include "test_iterators.h"
+
+template <class Iter, class Sent = sentinel_wrapper<Iter>>
+constexpr void testOne() {
+  using Range          = std::ranges::subrange<Iter, Sent>;
+  std::tuple<int> ts[] = {{1}, {2}, {3}};
+  auto ev              = Range{Iter{&ts[0]}, Sent{Iter{&ts[0] + 3}}} | std::views::elements<0>;
+
+  using ElementIter = std::ranges::iterator_t<decltype(ev)>;
+
+  // ++i
+  {
+    auto it               = ev.begin();
+    decltype(auto) result = ++it;
+
+    static_assert(std::is_same_v<decltype(result), ElementIter&>);
+    assert(&result == &it);
+
+    assert(base(it.base()) == &ts[1]);
+  }
+
+  // i++
+  {
+    if constexpr (std::forward_iterator<Iter>) {
+      auto it               = ev.begin();
+      decltype(auto) result = it++;
+
+      static_assert(std::is_same_v<decltype(result), ElementIter>);
+
+      assert(base(it.base()) == &ts[1]);
+      assert(base(result.base()) == &ts[0]);
+    } else {
+      auto it = ev.begin();
+      it++;
+
+      static_assert(std::is_same_v<decltype(it++), void>);
+      assert(base(it.base()) == &ts[1]);
+    }
+  }
+}
+
+constexpr bool test() {
+  using Ptr = std::tuple<int>*;
+  testOne<cpp20_input_iterator<Ptr>>();
+  testOne<forward_iterator<Ptr>>();
+  testOne<bidirectional_iterator<Ptr>>();
+  testOne<random_access_iterator<Ptr>>();
+  testOne<contiguous_iterator<Ptr>>();
+  testOne<Ptr>();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/member_types.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/member_types.compile.pass.cpp
new file mode 100644
index 0000000000000..67ec179b50b6b
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/member_types.compile.pass.cpp
@@ -0,0 +1,89 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// Member typedefs in elements_view<V, N>::iterator.
+
+#include <concepts>
+#include <ranges>
+#include <tuple>
+
+#include "test_macros.h"
+#include "test_iterators.h"
+
+template <class Iter>
+using Range = std::ranges::subrange<Iter, sentinel_wrapper<Iter>>;
+
+template <class Range, size_t N = 0>
+using ElementsIter = std::ranges::iterator_t<std::ranges::elements_view<Range, N>>;
+
+// using iterator_concept = see below;
+static_assert(std::same_as<ElementsIter<Range<cpp20_input_iterator<std::tuple<int>*>>>::iterator_concept,
+                           std::input_iterator_tag>);
+
+static_assert(std::same_as<ElementsIter<Range<forward_iterator<std::tuple<int>*>>>::iterator_concept, //
+                           std::forward_iterator_tag>);
+
+static_assert(std::same_as<ElementsIter<Range<bidirectional_iterator<std::tuple<int>*>>>::iterator_concept,
+                           std::bidirectional_iterator_tag>);
+
+static_assert(std::same_as<ElementsIter<Range<random_access_iterator<std::tuple<int>*>>>::iterator_concept,
+                           std::random_access_iterator_tag>);
+
+static_assert(std::same_as<ElementsIter<Range<contiguous_iterator<std::tuple<int>*>>>::iterator_concept,
+                           std::random_access_iterator_tag>);
+
+static_assert(std::same_as<ElementsIter<Range<std::tuple<int>*>>::iterator_concept, //
+                           std::random_access_iterator_tag>);
+
+// using iterator_category = see below;   // not always present
+template <class T>
+concept HasIterCategory = requires { typename T::iterator_category; };
+
+static_assert(!HasIterCategory<ElementsIter<Range<cpp20_input_iterator<std::tuple<int>*>>>>);
+static_assert(HasIterCategory<ElementsIter<Range<forward_iterator<std::tuple<int>*>>>>);
+
+static_assert(std::same_as<ElementsIter<Range<forward_iterator<std::tuple<int>*>>>::iterator_category,
+                           std::forward_iterator_tag>);
+
+static_assert(std::same_as<ElementsIter<Range<bidirectional_iterator<std::tuple<int>*>>>::iterator_category,
+                           std::bidirectional_iterator_tag>);
+
+static_assert(std::same_as<ElementsIter<Range<random_access_iterator<std::tuple<int>*>>>::iterator_category,
+                           std::random_access_iterator_tag>);
+
+static_assert(std::same_as<ElementsIter<Range<contiguous_iterator<std::tuple<int>*>>>::iterator_category,
+                           std::random_access_iterator_tag>);
+
+static_assert(std::same_as<ElementsIter<Range<std::tuple<int>*>>::iterator_category, //
+                           std::random_access_iterator_tag>);
+
+using Generator = decltype(std::views::iota(0, 1) | std::views::transform([](int) {
+                             return std::pair<int, short>{1, 1};
+                           }));
+static_assert(std::ranges::random_access_range<Generator>);
+
+static_assert(std::same_as<ElementsIter<Generator>::iterator_category, //
+                           std::input_iterator_tag>);
+
+// using value_type = remove_cvref_t<tuple_element_t<N, range_value_t<Base>>>;
+static_assert(std::same_as<ElementsIter<Range<std::tuple<int, long>*>, 0>::value_type, int>);
+
+static_assert(std::same_as<ElementsIter<Range<std::tuple<int, long>*>, 1>::value_type, long>);
+
+static_assert(std::same_as<ElementsIter<Generator, 0>::value_type, int>);
+
+static_assert(std::same_as<ElementsIter<Generator, 1>::value_type, short>);
+
+// using 
diff erence_type = range_
diff erence_t<Base>;
+static_assert(std::same_as<ElementsIter<Range<std::tuple<int, long>*>>::
diff erence_type,
+                           std::ranges::range_
diff erence_t<Range<std::tuple<int, long>*>>>);
+
+static_assert(std::same_as<ElementsIter<Generator>::
diff erence_type, //
+                           std::ranges::range_
diff erence_t<Generator>>);

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/subscript.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/subscript.pass.cpp
new file mode 100644
index 0000000000000..e456ead0e4c52
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/iterator/subscript.pass.cpp
@@ -0,0 +1,69 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr decltype(auto) operator[](
diff erence_type n) const
+//   requires random_access_range<Base>
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+
+#include "test_iterators.h"
+
+template <class T, class U>
+concept CanSubscript = requires(T t, U u) { t[u]; };
+
+template <class BaseRange>
+using ElemIter = std::ranges::iterator_t<std::ranges::elements_view<BaseRange, 0>>;
+
+using RandomAccessRange = std::ranges::subrange<std::tuple<int>*>;
+static_assert(std::ranges::random_access_range<RandomAccessRange>);
+
+static_assert(CanSubscript<ElemIter<RandomAccessRange>, int>);
+
+using BidiRange = std::ranges::subrange<bidirectional_iterator<std::tuple<int>*>>;
+static_assert(!std::ranges::random_access_range<BidiRange>);
+
+static_assert(!CanSubscript<ElemIter<BidiRange>, int>);
+
+constexpr bool test() {
+  {
+    // reference
+    std::tuple<int> ts[] = {{1}, {2}, {3}, {4}};
+    auto ev              = ts | std::views::elements<0>;
+    auto it              = ev.begin();
+
+    assert(&it[0] == &*it);
+    assert(&it[2] == &*(it + 2));
+
+    static_assert(std::is_same_v<decltype(it[2]), int&>);
+  }
+
+  {
+    // value
+    auto ev = std::views::iota(0, 5) | std::views::transform([](int i) { return std::tuple<int>{i}; }) |
+              std::views::elements<0>;
+    auto it = ev.begin();
+    assert(it[0] == *it);
+    assert(it[2] == *(it + 2));
+    assert(it[4] == *(it + 4));
+
+    static_assert(std::is_same_v<decltype(it[2]), int>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/range.concept.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/range.concept.compile.pass.cpp
new file mode 100644
index 0000000000000..021f0283b4336
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/range.concept.compile.pass.cpp
@@ -0,0 +1,75 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// concept checking
+//
+// template<class T, size_t N>
+// concept has-tuple-element =
+//   tuple-like<T> && N < tuple_size_v<T>;
+//
+// template<class T, size_t N>
+// concept returnable-element =
+//   is_reference_v<T> || move_constructible<tuple_element_t<N, T>>;
+//
+// template<input_range V, size_t N>
+//   requires view<V> && has-tuple-element<range_value_t<V>, N> &&
+//            has-tuple-element<remove_reference_t<range_reference_t<V>>, N> &&
+//            returnable-element<range_reference_t<V>, N>
+// class elements_view;
+
+#include <array>
+#include <concepts>
+#include <tuple>
+#include <ranges>
+#include <utility>
+
+#include "test_iterators.h"
+
+template <class It>
+using Range = std::ranges::subrange<It, sentinel_wrapper<It>>;
+
+template <class V, size_t N>
+concept HasElementsView = requires { typename std::ranges::elements_view<V, N>; };
+
+static_assert(HasElementsView<Range<std::ranges::subrange<int*>*>, 0>);
+static_assert(HasElementsView<Range<std::pair<int, int>*>, 1>);
+static_assert(HasElementsView<Range<std::tuple<int, int, int>*>, 2>);
+static_assert(HasElementsView<Range<std::array<int, 4>*>, 3>);
+
+// !view<V>
+static_assert(!std::ranges::view<std::array<std::tuple<int>, 1>>);
+static_assert(!HasElementsView<std::array<std::tuple<int>, 1>, 0>);
+
+// !input_range
+static_assert(!std::ranges::input_range<Range<cpp20_output_iterator<std::tuple<int>*>>>);
+static_assert(!HasElementsView<Range<cpp20_output_iterator<std::tuple<int>*>>, 0>);
+
+// !tuple-like
+LIBCPP_STATIC_ASSERT(!std::__tuple_like<int>);
+static_assert(!HasElementsView<Range<int*>, 1>);
+
+// !(N < tuple_size_v<T>)
+static_assert(!(2 < std::tuple_size_v< std::pair<int, int>>));
+static_assert(!HasElementsView<Range<std::pair<int, int>*>, 2>);
+
+// ! (is_reference_v<T> || move_constructible<tuple_element_t<N, T>>)
+struct NonMovable {
+  NonMovable(int) {}
+  NonMovable(NonMovable&&) = delete;
+};
+static_assert(!std::move_constructible<NonMovable>);
+
+using NonMovableGenerator =
+    decltype(std::views::iota(0, 1) | std::views::transform([](int) {
+               return std::pair<NonMovable, int>{1, 1};
+             }));
+
+static_assert(!HasElementsView<NonMovableGenerator, 0>);
+static_assert(HasElementsView<NonMovableGenerator, 1>);

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/base.pass.cpp
new file mode 100644
index 0000000000000..d4d0156fdb5be
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/base.pass.cpp
@@ -0,0 +1,41 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr sentinel_t<Base> base() const;
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+#include <type_traits>
+#include <utility>
+
+struct Sent {
+  int i;
+
+  friend constexpr bool operator==(std::tuple<int>*, const Sent&) { return true; }
+};
+
+constexpr bool test() {
+  using BaseRange = std::ranges::subrange<std::tuple<int>*, Sent>;
+  using EleRange  = std::ranges::elements_view<BaseRange, 0>;
+  using EleSent   = std::ranges::sentinel_t<EleRange>;
+
+  const EleSent st{Sent{5}};
+  std::same_as<Sent> decltype(auto) base = st.base();
+  assert(base.i == 5);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.base.pass.cpp
new file mode 100644
index 0000000000000..13fef1d53f121
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.base.pass.cpp
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr explicit sentinel(sentinel_t<Base> end);
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+#include <utility>
+
+struct Sent {
+  int i;
+
+  friend constexpr bool operator==(std::tuple<int>*, const Sent&) { return true; }
+};
+
+struct Range : std::ranges::view_base {
+  std::tuple<int>* begin() const;
+  Sent end();
+};
+
+// Test explicit
+
+static_assert(std::is_constructible_v<std::ranges::sentinel_t<std::ranges::elements_view<Range, 0>>, Sent>);
+static_assert(!std::is_convertible_v<Sent, std::ranges::sentinel_t<std::ranges::elements_view<Range, 0>>>);
+
+constexpr bool test() {
+  // base is init correctly
+  {
+    using R        = std::ranges::elements_view<Range, 0>;
+    using Sentinel = std::ranges::sentinel_t<R>;
+
+    Sentinel s1(Sent{5});
+    assert(s1.base().i == 5);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.convert.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.convert.pass.cpp
new file mode 100644
index 0000000000000..4ff65be645220
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.convert.pass.cpp
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr sentinel(sentinel<!Const> s)
+//   requires Const && convertible_to<sentinel_t<V>, sentinel_t<Base>>;
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+
+#include "../types.h"
+
+struct Sent {
+  int i;
+  constexpr Sent() = default;
+  constexpr Sent(int ii) : i(ii) {}
+  friend constexpr bool operator==(std::tuple<int>*, const Sent&) { return true; }
+};
+
+struct ConstSent {
+  int i;
+  constexpr ConstSent() = default;
+  constexpr ConstSent(int ii) : i(ii) {}
+  constexpr ConstSent(const Sent& s) : i(s.i) {}
+  friend constexpr bool operator==(std::tuple<int>*, const ConstSent&) { return true; }
+};
+
+struct Range : std::ranges::view_base {
+  std::tuple<int>* begin() const;
+  Sent end();
+  ConstSent end() const;
+};
+
+struct NonConvertConstSent {
+  int i;
+  constexpr NonConvertConstSent() = default;
+  constexpr NonConvertConstSent(int ii) : i(ii) {}
+  friend constexpr bool operator==(std::tuple<int>*, const NonConvertConstSent&) { return true; }
+};
+
+struct NonConvertConstSentRange : std::ranges::view_base {
+  std::tuple<int>* begin() const;
+  Sent end();
+  NonConvertConstSent end() const;
+};
+
+// Test Constraint
+static_assert(std::is_constructible_v<std::ranges::sentinel_t<const std::ranges::elements_view<Range, 0>>,
+                                      std::ranges::sentinel_t<std::ranges::elements_view<Range, 0>>>);
+
+// !Const
+static_assert(!std::is_constructible_v<std::ranges::sentinel_t<std::ranges::elements_view<Range, 0>>,
+                                       std::ranges::sentinel_t<const std::ranges::elements_view<Range, 0>>>);
+
+// !convertible_to<sentinel_t<V>, sentinel_t<Base>>
+static_assert(!std::is_constructible_v<
+              std::ranges::sentinel_t<const std::ranges::elements_view<NonConvertConstSentRange, 0>>,
+              std::ranges::sentinel_t<std::ranges::elements_view<NonConvertConstSentRange, 0>>>);
+
+constexpr bool test() {
+  // base is init correctly
+  {
+    using R             = std::ranges::elements_view<Range, 0>;
+    using Sentinel      = std::ranges::sentinel_t<R>;
+    using ConstSentinel = std::ranges::sentinel_t<const R>;
+    static_assert(!std::same_as<Sentinel, ConstSentinel>);
+
+    Sentinel s1(Sent{5});
+    ConstSentinel s2 = s1;
+    assert(s2.base().i == 5);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..b2f4eae51baee
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/ctor.default.pass.cpp
@@ -0,0 +1,49 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// sentinel() = default;
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+
+struct PODSentinel {
+  int i; // deliberately uninitialised
+
+  friend constexpr bool operator==(std::tuple<int>*, const PODSentinel&) { return true; }
+};
+
+struct Range : std::ranges::view_base {
+  std::tuple<int>* begin() const;
+  PODSentinel end();
+};
+
+constexpr bool test() {
+  using EleView  = std::ranges::elements_view<Range, 0>;
+  using Sentinel = std::ranges::sentinel_t<EleView>;
+  static_assert(!std::is_same_v<Sentinel, std::ranges::iterator_t<EleView>>);
+
+  {
+    Sentinel s;
+    assert(s.base().i == 0);
+  }
+  {
+    Sentinel s = {};
+    assert(s.base().i == 0);
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/equality.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/equality.pass.cpp
new file mode 100644
index 0000000000000..55477cc997587
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/equality.pass.cpp
@@ -0,0 +1,164 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+//  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 <array>
+#include <cassert>
+#include <ranges>
+
+#include "../types.h"
+
+template <bool Const>
+struct Iter {
+  std::tuple<int>* it_;
+
+  using value_type       = std::tuple<int>;
+  using 
diff erence_type  = intptr_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 {
+  std::tuple<int>* end_;
+
+  constexpr bool operator==(const Iter<Const>& i) const { return i.it_ == end_; }
+};
+
+template <bool Const>
+struct CrossComparableSent {
+  std::tuple<int>* end_;
+
+  template <bool C>
+  constexpr bool operator==(const Iter<C>& i) const {
+    return i.it_ == end_;
+  }
+};
+
+template <template <bool> typename St>
+struct Range : TupleBufferView {
+  using TupleBufferView::TupleBufferView;
+  constexpr Iter<false> begin() { return Iter<false>{buffer_}; }
+  constexpr Iter<true> begin() const { return Iter<true>{buffer_}; }
+  constexpr St<false> end() { return St<false>{buffer_ + size_}; }
+  constexpr St<true> end() const { return St<true>{buffer_ + size_}; }
+};
+
+using R                = Range<Sent>;
+using CrossComparableR = Range<CrossComparableSent>;
+
+// Test Constraint
+template <class I, class S>
+concept HasEqual = requires(const I i, const S s) { i == s; };
+
+using std::ranges::elements_view;
+using std::ranges::iterator_t;
+using std::ranges::sentinel_t;
+
+static_assert(HasEqual<iterator_t<elements_view<R, 0>>, //
+                       sentinel_t<elements_view<R, 0>>>);
+
+static_assert(!HasEqual<iterator_t<const elements_view<R, 0>>, //
+                        sentinel_t<elements_view<R, 0>>>);
+
+static_assert(!HasEqual<iterator_t<elements_view<R, 0>>, //
+                        sentinel_t<const elements_view<R, 0>>>);
+
+static_assert(HasEqual<iterator_t<const elements_view<R, 0>>, //
+                       sentinel_t<const elements_view<R, 0>>>);
+
+static_assert(HasEqual<iterator_t<elements_view<R, 0>>, //
+                       sentinel_t<elements_view<R, 0>>>);
+
+static_assert(HasEqual<iterator_t<const elements_view<CrossComparableR, 0>>, //
+                       sentinel_t<elements_view<CrossComparableR, 0>>>);
+
+static_assert(HasEqual<iterator_t<elements_view<CrossComparableR, 0>>, //
+                       sentinel_t<const elements_view<CrossComparableR, 0>>>);
+
+static_assert(HasEqual<iterator_t<const elements_view<CrossComparableR, 0>>, //
+                       sentinel_t<const elements_view<CrossComparableR, 0>>>);
+
+template <class R, bool ConstIter, bool ConstSent>
+constexpr void testOne() {
+  auto getBegin = [](auto&& rng) {
+    if constexpr (ConstIter) {
+      return std::as_const(rng).begin();
+    } else {
+      return rng.begin();
+    }
+  };
+
+  auto getEnd = [](auto&& rng) {
+    if constexpr (ConstSent) {
+      return std::as_const(rng).end();
+    } else {
+      return rng.end();
+    }
+  };
+
+  // iter == sentinel.base
+  {
+    std::tuple<int> buffer[] = {{1}};
+    R v{buffer};
+    std::ranges::elements_view<R, 0> ev(v);
+    auto iter = getBegin(ev);
+    auto st   = getEnd(ev);
+    ++iter;
+    assert(iter == st);
+  }
+
+  // iter != sentinel.base
+  {
+    std::tuple<int> buffer[] = {{1}};
+    R v{buffer};
+    std::ranges::elements_view<R, 0> ev(v);
+    auto iter = getBegin(ev);
+    auto st   = getEnd(ev);
+    assert(iter != st);
+  }
+
+  // empty range
+  {
+    std::array<std::tuple<int>, 0> arr;
+    R v{arr};
+    std::ranges::elements_view<R, 0> ev(v);
+    auto iter = getBegin(ev);
+    auto sent = getEnd(ev);
+    assert(iter == sent);
+  }
+}
+
+constexpr bool test() {
+  testOne<R, false, false>();
+  testOne<R, true, true>();
+  testOne<CrossComparableR, false, false>();
+  testOne<CrossComparableR, true, true>();
+  testOne<CrossComparableR, true, false>();
+  testOne<CrossComparableR, false, true>();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/minus.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/minus.pass.cpp
new file mode 100644
index 0000000000000..9af7b097aa4f2
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/sentinel/minus.pass.cpp
@@ -0,0 +1,213 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// template<bool OtherConst>
+//   requires sized_sentinel_for<sentinel_t<Base>, iterator_t<maybe-const<OtherConst, V>>>
+// friend constexpr range_
diff erence_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_
diff erence_t<maybe-const<OtherConst, V>>
+//   operator-(const sentinel& x, const iterator<OtherConst>& y);
+
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <ranges>
+#include <tuple>
+
+#include "../types.h"
+
+template <bool Const>
+struct Iter {
+  std::tuple<int>* it_;
+
+  using value_type       = std::tuple<int>;
+  using 
diff erence_type  = intptr_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 {
+  std::tuple<int>* end_;
+
+  constexpr bool operator==(const Iter<Const>& i) const { return i.it_ == end_; }
+};
+
+template <bool Const>
+struct SizedSent {
+  std::tuple<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 {
+  std::tuple<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 <template <bool> class It, template <bool> class St>
+struct Range : TupleBufferView {
+  using TupleBufferView::TupleBufferView;
+
+  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 ElementsView = std::ranges::elements_view<BaseRange, 0>;
+
+template <class BaseRange>
+using ElemIter = std::ranges::iterator_t<ElementsView<BaseRange>>;
+
+template <class BaseRange>
+using EleConstIter = std::ranges::iterator_t<const ElementsView<BaseRange>>;
+
+template <class BaseRange>
+using EleSent = std::ranges::sentinel_t<ElementsView<BaseRange>>;
+
+template <class BaseRange>
+using EleConstSent = std::ranges::sentinel_t<const ElementsView<BaseRange>>;
+
+constexpr void testConstraints() {
+  // base is not sized
+  {
+    using Base = Range<Iter, Sent>;
+    static_assert(!HasMinus<EleSent<Base>, ElemIter<Base>>);
+    static_assert(!HasMinus<ElemIter<Base>, EleSent<Base>>);
+
+    static_assert(!HasMinus<EleSent<Base>, EleConstIter<Base>>);
+    static_assert(!HasMinus<EleConstIter<Base>, EleSent<Base>>);
+
+    static_assert(!HasMinus<EleConstSent<Base>, EleConstIter<Base>>);
+    static_assert(!HasMinus<EleConstIter<Base>, EleConstSent<Base>>);
+
+    static_assert(!HasMinus<EleConstSent<Base>, ElemIter<Base>>);
+    static_assert(!HasMinus<ElemIter<Base>, EleConstSent<Base>>);
+  }
+
+  // base is sized but not cross const
+  {
+    using Base = Range<Iter, SizedSent>;
+    static_assert(HasMinus<EleSent<Base>, ElemIter<Base>>);
+    static_assert(HasMinus<ElemIter<Base>, EleSent<Base>>);
+
+    static_assert(!HasMinus<EleSent<Base>, EleConstIter<Base>>);
+    static_assert(!HasMinus<EleConstIter<Base>, EleSent<Base>>);
+
+    static_assert(HasMinus<EleConstSent<Base>, EleConstIter<Base>>);
+    static_assert(HasMinus<EleConstIter<Base>, EleConstSent<Base>>);
+
+    static_assert(!HasMinus<EleConstSent<Base>, ElemIter<Base>>);
+    static_assert(!HasMinus<ElemIter<Base>, EleConstSent<Base>>);
+  }
+
+  // base is cross const sized
+  {
+    using Base = Range<Iter, CrossSizedSent>;
+    static_assert(HasMinus<EleSent<Base>, ElemIter<Base>>);
+    static_assert(HasMinus<ElemIter<Base>, EleSent<Base>>);
+
+    static_assert(HasMinus<EleSent<Base>, EleConstIter<Base>>);
+    static_assert(HasMinus<EleConstIter<Base>, EleSent<Base>>);
+
+    static_assert(HasMinus<EleConstSent<Base>, EleConstIter<Base>>);
+    static_assert(HasMinus<EleConstIter<Base>, EleConstSent<Base>>);
+
+    static_assert(HasMinus<EleConstSent<Base>, ElemIter<Base>>);
+    static_assert(HasMinus<ElemIter<Base>, EleConstSent<Base>>);
+  }
+}
+
+constexpr bool test() {
+  std::tuple<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::elements<0>;
+    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::elements<0>;
+    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.elements/size.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.elements/size.pass.cpp
new file mode 100644
index 0000000000000..027cb935506fe
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/size.pass.cpp
@@ -0,0 +1,88 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// constexpr auto size() requires sized_range<V>
+// constexpr auto size() const requires sized_range<const V>
+
+#include <cassert>
+#include <ranges>
+#include <tuple>
+#include <utility>
+
+#include "types.h"
+
+template <class T>
+concept HasSize = requires(T t) { t.size(); };
+
+static_assert(HasSize<std::ranges::elements_view<SimpleCommon, 0>>);
+static_assert(HasSize<const std::ranges::elements_view<SimpleCommon, 0>>);
+
+struct NonSized : std::ranges::view_base {
+  using iterator = forward_iterator<std::tuple<int>*>;
+  iterator begin() const;
+  iterator end() const;
+};
+static_assert(!std::ranges::sized_range<NonSized>);
+static_assert(!std::ranges::sized_range<const NonSized>);
+
+static_assert(!HasSize<std::ranges::elements_view<NonSized, 0>>);
+static_assert(!HasSize<const std::ranges::elements_view<NonSized, 0>>);
+
+struct SizedNonConst : TupleBufferView {
+  using TupleBufferView::TupleBufferView;
+
+  using iterator = forward_iterator<std::tuple<int>*>;
+  constexpr auto begin() const { return iterator{buffer_}; }
+  constexpr auto end() const { return iterator{buffer_ + size_}; }
+  constexpr std::size_t size() { return size_; }
+};
+
+static_assert(HasSize<std::ranges::elements_view<SizedNonConst, 0>>);
+static_assert(!HasSize<const std::ranges::elements_view<SizedNonConst, 0>>);
+
+struct OnlyConstSized : TupleBufferView {
+  using TupleBufferView::TupleBufferView;
+
+  using iterator = forward_iterator<std::tuple<int>*>;
+  constexpr auto begin() const { return iterator{buffer_}; }
+  constexpr auto end() const { return iterator{buffer_ + size_}; }
+  constexpr std::size_t size() const { return size_; }
+  constexpr std::size_t size() = delete;
+};
+
+static_assert(HasSize<const OnlyConstSized>);
+static_assert(HasSize<std::ranges::elements_view<OnlyConstSized, 0>>);
+static_assert(HasSize<const std::ranges::elements_view<OnlyConstSized, 0>>);
+
+constexpr bool test() {
+  std::tuple<int> buffer[] = {{1}, {2}, {3}};
+
+  // non-const and const are sized
+  {
+    auto ev = std::views::elements<0>(buffer);
+    assert(ev.size() == 3);
+    assert(std::as_const(ev).size() == 3);
+  }
+
+  {
+    // const-view non-sized range
+    auto ev = std::views::elements<0>(SizedNonConst{buffer});
+    assert(ev.size() == 3);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.elements/types.h b/libcxx/test/std/ranges/range.adaptors/range.elements/types.h
new file mode 100644
index 0000000000000..a1c0884b60719
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.elements/types.h
@@ -0,0 +1,106 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_ELEMENTS_TYPES_H
+#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_ELEMENTS_TYPES_H
+
+#include <array>
+#include <functional>
+#include <ranges>
+#include <tuple>
+
+#include "test_macros.h"
+#include "test_iterators.h"
+#include "test_range.h"
+
+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) {}
+};
+
+using TupleBufferView = BufferView<std::tuple<int>>;
+
+template <bool Simple>
+struct Common : TupleBufferView {
+  using TupleBufferView::TupleBufferView;
+
+  constexpr std::tuple<int>* begin()
+    requires(!Simple)
+  {
+    return buffer_;
+  }
+  constexpr const std::tuple<int>* begin() const { return buffer_; }
+  constexpr std::tuple<int>* end()
+    requires(!Simple)
+  {
+    return buffer_ + size_;
+  }
+  constexpr const std::tuple<int>* end() const { return buffer_ + size_; }
+};
+using SimpleCommon    = Common<true>;
+using NonSimpleCommon = Common<false>;
+
+using SimpleCommonRandomAccessSized    = SimpleCommon;
+using NonSimpleCommonRandomAccessSized = NonSimpleCommon;
+
+static_assert(std::ranges::common_range<Common<true>>);
+static_assert(std::ranges::random_access_range<SimpleCommon>);
+static_assert(std::ranges::sized_range<SimpleCommon>);
+LIBCPP_STATIC_ASSERT(std::ranges::__simple_view<SimpleCommon>);
+LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view<NonSimpleCommon>);
+
+template <bool Simple>
+struct NonCommon : TupleBufferView {
+  using TupleBufferView::TupleBufferView;
+  constexpr std::tuple<int>* begin()
+    requires(!Simple)
+  {
+    return buffer_;
+  }
+  constexpr const std::tuple<int>* begin() const { return buffer_; }
+  constexpr sentinel_wrapper<std::tuple<int>*> end()
+    requires(!Simple)
+  {
+    return sentinel_wrapper<std::tuple<int>*>(buffer_ + size_);
+  }
+  constexpr sentinel_wrapper<const std::tuple<int>*> end() const {
+    return sentinel_wrapper<const std::tuple<int>*>(buffer_ + size_);
+  }
+};
+
+using SimpleNonCommon    = NonCommon<true>;
+using NonSimpleNonCommon = NonCommon<false>;
+
+static_assert(!std::ranges::common_range<SimpleNonCommon>);
+static_assert(std::ranges::random_access_range<SimpleNonCommon>);
+static_assert(!std::ranges::sized_range<SimpleNonCommon>);
+LIBCPP_STATIC_ASSERT(std::ranges::__simple_view<SimpleNonCommon>);
+LIBCPP_STATIC_ASSERT(!std::ranges::__simple_view<NonSimpleNonCommon>);
+
+template <class Derived>
+struct IterBase {
+  using iterator_concept = std::random_access_iterator_tag;
+  using value_type       = std::tuple<int>;
+  using 
diff erence_type  = intptr_t;
+
+  constexpr std::tuple<int> operator*() const { return std::tuple<int>(5); }
+
+  constexpr Derived& operator++() { return *this; }
+  constexpr void operator++(int) {}
+
+  friend constexpr bool operator==(const IterBase&, const IterBase&) = default;
+};
+
+#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_ELEMENTS_TYPES_H

diff  --git a/llvm/utils/gn/secondary/libcxx/include/BUILD.gn b/llvm/utils/gn/secondary/libcxx/include/BUILD.gn
index 9d9fda0e3efc5..ffd2d822cc472 100644
--- a/llvm/utils/gn/secondary/libcxx/include/BUILD.gn
+++ b/llvm/utils/gn/secondary/libcxx/include/BUILD.gn
@@ -629,10 +629,12 @@ if (current_toolchain == default_toolchain) {
       "__tree",
       "__tuple_dir/apply_cv.h",
       "__tuple_dir/make_tuple_types.h",
+      "__tuple_dir/pair_like.h",
       "__tuple_dir/sfinae_helpers.h",
       "__tuple_dir/tuple_element.h",
       "__tuple_dir/tuple_indices.h",
       "__tuple_dir/tuple_like.h",
+      "__tuple_dir/tuple_like_ext.h",
       "__tuple_dir/tuple_size.h",
       "__tuple_dir/tuple_types.h",
       "__type_traits/add_const.h",


        


More information about the libcxx-commits mailing list