[libcxx-commits] [libcxx] a2b3ab8 - [libc++][ranges] implement `std::ranges::split_view`

via libcxx-commits libcxx-commits at lists.llvm.org
Tue Jan 24 01:03:52 PST 2023


Author: Hui
Date: 2023-01-24T09:03:33Z
New Revision: a2b3ab8f7786b9bb6e1b8bbb01b88d4bbe28af69

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

LOG: [libc++][ranges] implement `std::ranges::split_view`

- implement `std::ranges::split_view` (last c++20 view)
- Work in process on testing iterator/sentinel, but since we are
getting closer to the deadline, I'd like to send the review early

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

Added: 
    libcxx/include/__ranges/split_view.h
    libcxx/test/libcxx/ranges/range.adaptors/range.split/no_unique_address.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/adaptor.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/base.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/begin.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/constraints.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/ctad.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/ctor.default.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/ctor.range.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/ctor.view.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/end.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/general.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/iterator/base.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/iterator/ctor.base.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/iterator/ctor.default.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/iterator/deref.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/iterator/equal.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/iterator/increment.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/iterator/member_types.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/sentinel/ctor.default.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/sentinel/ctor.parent.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/sentinel/equal.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.split/types.h

Modified: 
    libcxx/docs/ReleaseNotes.rst
    libcxx/docs/Status/Cxx20Papers.csv
    libcxx/docs/Status/Cxx2bIssues.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/module.modulemap.in
    libcxx/include/ranges
    libcxx/test/libcxx/private_headers.verify.cpp

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/ReleaseNotes.rst b/libcxx/docs/ReleaseNotes.rst
index 345dd53d21884..c274f45c05ead 100644
--- a/libcxx/docs/ReleaseNotes.rst
+++ b/libcxx/docs/ReleaseNotes.rst
@@ -69,7 +69,8 @@ Implemented Papers
 - P1035R7 - Input Range Adaptors
 - P2325R3 - Views should not be required to be default constructible
 - P2446R2 - ``views::as_rvalue``
-- P1020 - Smart pointer creation with default initialization
+- P1020R1 - Smart pointer creation with default initialization
+- P2210R2 - Superior String Splitting
 
 Improvements and New Features
 -----------------------------

diff  --git a/libcxx/docs/Status/Cxx20Papers.csv b/libcxx/docs/Status/Cxx20Papers.csv
index 3999b013cb425..6b01966b69899 100644
--- a/libcxx/docs/Status/Cxx20Papers.csv
+++ b/libcxx/docs/Status/Cxx20Papers.csv
@@ -194,7 +194,7 @@
 "","","","","","",""
 "`P2231R1 <https://wg21.link/P2231R1>`__","LWG","Missing constexpr in std::optional and std::variant","June 2021","|Partial| [#note-P2231]_","13.0"
 "`P2325R3 <https://wg21.link/P2325R3>`__","LWG","Views should not be required to be default constructible","June 2021","|Complete|","16.0","|ranges|"
-"`P2210R2 <https://wg21.link/P2210R2>`__","LWG","Superior String Splitting","June 2021","|In progress|","","|ranges|"
+"`P2210R2 <https://wg21.link/P2210R2>`__","LWG","Superior String Splitting","June 2021","|Complete|","16.0","|ranges|"
 "`P2216R3 <https://wg21.link/P2216R3>`__","LWG","std::format improvements","June 2021","|Complete|","15.0"
 "`P2281R1 <https://wg21.link/P2281R1>`__","LWG","Clarifying range adaptor objects","June 2021","|Complete|","14.0","|ranges|"
 "`P2328R1 <https://wg21.link/P2328R1>`__","LWG","join_view should join all views of ranges","June 2021","|Complete|","15.0","|ranges|"

diff  --git a/libcxx/docs/Status/Cxx2bIssues.csv b/libcxx/docs/Status/Cxx2bIssues.csv
index 97a8afbebc45e..b2cc710151f6a 100644
--- a/libcxx/docs/Status/Cxx2bIssues.csv
+++ b/libcxx/docs/Status/Cxx2bIssues.csv
@@ -131,7 +131,7 @@
 `3581 <https://wg21.link/LWG3581>`__,"The range constructor makes ``basic_string_view`` not trivially move constructible","October 2021","|Complete|","14.0","|ranges|"
 `3585 <https://wg21.link/LWG3585>`__,"``variant`` converting assignment with immovable alternative","October 2021","",""
 `3589 <https://wg21.link/LWG3589>`__,"The ``const`` lvalue reference overload of ``get`` for ``subrange`` does not constrain ``I`` to be ``copyable`` when ``N == 0``","October 2021","|Complete|","14.0","|ranges|"
-`3590 <https://wg21.link/LWG3590>`__,"``split_view::base() const &`` is overconstrained","October 2021","","","|ranges|"
+`3590 <https://wg21.link/LWG3590>`__,"``split_view::base() const &`` is overconstrained","October 2021","|Complete|","16.0","|ranges|"
 `3591 <https://wg21.link/LWG3591>`__,"``lazy_split_view<input_view>::inner-iterator::base() &&`` invalidates outer iterators","October 2021","","","|ranges|"
 `3592 <https://wg21.link/LWG3592>`__,"``lazy_split_view`` needs to check the simpleness of Pattern","October 2021","","","|ranges|"
 `3593 <https://wg21.link/LWG3593>`__,"Several iterators' ``base() const &`` and ``lazy_split_view::outer-iterator::value_type::end()`` missing ``noexcept``","October 2021","","","|ranges|"

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 51bfc8ca53658..a12aa1de1356d 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -528,6 +528,7 @@ set(files
   __ranges/reverse_view.h
   __ranges/single_view.h
   __ranges/size.h
+  __ranges/split_view.h
   __ranges/subrange.h
   __ranges/take_view.h
   __ranges/take_while_view.h

diff  --git a/libcxx/include/__ranges/split_view.h b/libcxx/include/__ranges/split_view.h
new file mode 100644
index 0000000000000..9758ee935f299
--- /dev/null
+++ b/libcxx/include/__ranges/split_view.h
@@ -0,0 +1,232 @@
+// -*- 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_SPLIT_VIEW_H
+#define _LIBCPP___RANGES_SPLIT_VIEW_H
+
+#include <__algorithm/ranges_search.h>
+#include <__concepts/constructible.h>
+#include <__config>
+#include <__functional/bind_back.h>
+#include <__functional/ranges_operations.h>
+#include <__iterator/indirectly_comparable.h>
+#include <__iterator/iterator_traits.h>
+#include <__memory/addressof.h>
+#include <__ranges/access.h>
+#include <__ranges/all.h>
+#include <__ranges/concepts.h>
+#include <__ranges/empty.h>
+#include <__ranges/non_propagating_cache.h>
+#include <__ranges/range_adaptor.h>
+#include <__ranges/single_view.h>
+#include <__ranges/subrange.h>
+#include <__ranges/view_interface.h>
+#include <__type_traits/decay.h>
+#include <__type_traits/is_nothrow_constructible.h>
+#include <__utility/forward.h>
+#include <__utility/move.h>
+
+#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, class _Pattern>
+struct __split_view_iterator;
+
+template <class _View, class _Pattern>
+struct __split_view_sentinel;
+
+template <forward_range _View, forward_range _Pattern>
+  requires view<_View> && view<_Pattern> &&
+           indirectly_comparable<iterator_t<_View>, iterator_t<_Pattern>, ranges::equal_to>
+class split_view : public view_interface<split_view<_View, _Pattern>> {
+private:
+  _LIBCPP_NO_UNIQUE_ADDRESS _View __base_       = _View();
+  _LIBCPP_NO_UNIQUE_ADDRESS _Pattern __pattern_ = _Pattern();
+  using _Cache                                  = __non_propagating_cache<subrange<iterator_t<_View>>>;
+  _Cache __cached_begin_                        = _Cache();
+
+  template <class, class>
+  friend struct __split_view_iterator;
+
+  template <class, class>
+  friend struct __split_view_sentinel;
+
+  using __iterator = __split_view_iterator<_View, _Pattern>;
+  using __sentinel = __split_view_sentinel<_View, _Pattern>;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr subrange<iterator_t<_View>> __find_next(iterator_t<_View> __it) {
+    auto [__begin, __end] = ranges::search(subrange(__it, ranges::end(__base_)), __pattern_);
+    if (__begin != ranges::end(__base_) && ranges::empty(__pattern_)) {
+      ++__begin;
+      ++__end;
+    }
+    return {__begin, __end};
+  }
+
+public:
+  _LIBCPP_HIDE_FROM_ABI split_view()
+    requires default_initializable<_View> && default_initializable<_Pattern>
+  = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr split_view(_View __base, _Pattern __pattern)
+      : __base_(std::move(__base)), __pattern_(std::move((__pattern))) {}
+
+  template <forward_range _Range>
+    requires constructible_from<_View, views::all_t<_Range>> &&
+                 constructible_from<_Pattern, single_view<range_value_t<_Range>>>
+  _LIBCPP_HIDE_FROM_ABI constexpr split_view(_Range&& __range, range_value_t<_Range> __elem)
+      : __base_(views::all(std::forward<_Range>(__range))), __pattern_(views::single(std::move(__elem))) {}
+
+  _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 __iterator begin() {
+    if (!__cached_begin_.__has_value()) {
+      __cached_begin_.__emplace(__find_next(ranges::begin(__base_)));
+    }
+    return {*this, ranges::begin(__base_), *__cached_begin_};
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto end() {
+    if constexpr (common_range<_View>) {
+      return __iterator{*this, ranges::end(__base_), {}};
+    } else {
+      return __sentinel{*this};
+    }
+  }
+};
+
+template <class _Range, class _Pattern>
+split_view(_Range&&, _Pattern&&) -> split_view<views::all_t<_Range>, views::all_t<_Pattern>>;
+
+template <forward_range _Range>
+split_view(_Range&&, range_value_t<_Range>) -> split_view<views::all_t<_Range>, single_view<range_value_t<_Range>>>;
+
+template <class _View, class _Pattern>
+struct __split_view_iterator {
+private:
+  split_view<_View, _Pattern>* __parent_                        = nullptr;
+  _LIBCPP_NO_UNIQUE_ADDRESS iterator_t<_View> __cur_            = iterator_t<_View>();
+  _LIBCPP_NO_UNIQUE_ADDRESS subrange<iterator_t<_View>> __next_ = subrange<iterator_t<_View>>();
+  bool __trailing_empty_                                        = false;
+
+  template <class, class>
+  friend struct __split_view_sentinel;
+
+public:
+  using iterator_concept  = forward_iterator_tag;
+  using iterator_category = input_iterator_tag;
+  using value_type        = subrange<iterator_t<_View>>;
+  using 
diff erence_type   = range_
diff erence_t<_View>;
+
+  _LIBCPP_HIDE_FROM_ABI __split_view_iterator() = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __split_view_iterator(
+      split_view<_View, _Pattern>& __parent, iterator_t<_View> __current, subrange<iterator_t<_View>> __next)
+      : __parent_(std::addressof(__parent)), __cur_(std::move(__current)), __next_(std::move(__next)) {}
+
+  _LIBCPP_HIDE_FROM_ABI constexpr iterator_t<_View> base() const { return __cur_; }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr value_type operator*() const { return {__cur_, __next_.begin()}; }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __split_view_iterator& operator++() {
+    __cur_ = __next_.begin();
+    if (__cur_ != ranges::end(__parent_->__base_)) {
+      __cur_ = __next_.end();
+      if (__cur_ == ranges::end(__parent_->__base_)) {
+        __trailing_empty_ = true;
+        __next_           = {__cur_, __cur_};
+      } else {
+        __next_ = __parent_->__find_next(__cur_);
+      }
+    } else {
+      __trailing_empty_ = false;
+    }
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __split_view_iterator operator++(int) {
+    auto __tmp = *this;
+    ++*this;
+    return __tmp;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator==(const __split_view_iterator& __x, const __split_view_iterator& __y) {
+    return __x.__cur_ == __y.__cur_ && __x.__trailing_empty_ == __y.__trailing_empty_;
+  }
+};
+
+template <class _View, class _Pattern>
+struct __split_view_sentinel {
+private:
+  _LIBCPP_NO_UNIQUE_ADDRESS sentinel_t<_View> __end_ = sentinel_t<_View>();
+
+  _LIBCPP_HIDE_FROM_ABI static constexpr bool
+  __equals(const __split_view_iterator<_View, _Pattern>& __x, const __split_view_sentinel& __y) {
+    return __x.__cur_ == __y.__end_ && !__x.__trailing_empty_;
+  }
+
+public:
+  _LIBCPP_HIDE_FROM_ABI __split_view_sentinel() = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit __split_view_sentinel(split_view<_View, _Pattern>& __parent)
+      : __end_(ranges::end(__parent.__base_)) {}
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator==(const __split_view_iterator<_View, _Pattern>& __x, const __split_view_sentinel& __y) {
+    return __equals(__x, __y);
+  }
+};
+
+namespace views {
+namespace __split_view {
+struct __fn : __range_adaptor_closure<__fn> {
+  // clang-format off
+  template <class _Range, class _Pattern>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI
+  constexpr auto operator()(_Range&& __range, _Pattern&& __pattern) const
+    noexcept(noexcept(split_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern))))
+    -> decltype(      split_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern)))
+    { return          split_view(std::forward<_Range>(__range), std::forward<_Pattern>(__pattern)); }
+  // clang-format on
+
+  template <class _Pattern>
+    requires constructible_from<decay_t<_Pattern>, _Pattern>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Pattern&& __pattern) const
+      noexcept(is_nothrow_constructible_v<decay_t<_Pattern>, _Pattern>) {
+    return __range_adaptor_closure_t(std::__bind_back(*this, std::forward<_Pattern>(__pattern)));
+  }
+};
+} // namespace __split_view
+
+inline namespace __cpo {
+inline constexpr auto split = __split_view::__fn{};
+} // namespace __cpo
+} // namespace views
+
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 20
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___RANGES_SPLIT_VIEW_H

diff  --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 5715775443ec3..1f1d67dbb7fcd 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1256,6 +1256,7 @@ 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 split_view             { private header "__ranges/split_view.h" }
       module subrange               {
         private header "__ranges/subrange.h"
 

diff  --git a/libcxx/include/ranges b/libcxx/include/ranges
index 99c16872a6078..f999fa00c3356 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -265,8 +265,15 @@ namespace std::ranges {
              (forward_range<V> || tiny-range<Pattern>)
   class lazy_split_view;
 
+  // [range.split], split view
+  template<forward_range V, forward_range Pattern>
+    requires view<V> && view<Pattern> &&
+             indirectly_comparable<iterator_t<V>, iterator_t<Pattern>, ranges::equal_to>
+  class split_view;
+
   namespace views {
     inline constexpr unspecified lazy_split = unspecified;
+    inline constexpr unspecified split = unspecified;
   }
 
   // [range.istream], istream view
@@ -360,6 +367,7 @@ namespace std {
 #include <__ranges/reverse_view.h>
 #include <__ranges/single_view.h>
 #include <__ranges/size.h>
+#include <__ranges/split_view.h>
 #include <__ranges/subrange.h>
 #include <__ranges/take_view.h>
 #include <__ranges/take_while_view.h>

diff  --git a/libcxx/test/libcxx/private_headers.verify.cpp b/libcxx/test/libcxx/private_headers.verify.cpp
index 1498faa71ddec..ebbd50bcb35b7 100644
--- a/libcxx/test/libcxx/private_headers.verify.cpp
+++ b/libcxx/test/libcxx/private_headers.verify.cpp
@@ -559,6 +559,7 @@ END-SCRIPT
 #include <__ranges/reverse_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/reverse_view.h'}}
 #include <__ranges/single_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/single_view.h'}}
 #include <__ranges/size.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/size.h'}}
+#include <__ranges/split_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/split_view.h'}}
 #include <__ranges/subrange.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/subrange.h'}}
 #include <__ranges/take_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/take_view.h'}}
 #include <__ranges/take_while_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/take_while_view.h'}}

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.split/no_unique_address.compile.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.split/no_unique_address.compile.pass.cpp
new file mode 100644
index 0000000000000..c04dbd7ac0d69
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.split/no_unique_address.compile.pass.cpp
@@ -0,0 +1,29 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// clang-cl and cl currently don't support [[no_unique_address]]
+// XFAIL: msvc
+
+// class split_view {
+//   _LIBCPP_NO_UNIQUE_ADDRESS _View __base_ = _View();
+//   _LIBCPP_NO_UNIQUE_ADDRESS _Pattern __pattern_ = _Pattern();
+// };
+
+#include <ranges>
+
+#include "test_iterators.h"
+
+struct EmptyView : std::ranges::view_base {
+  int* begin() const;
+  int* end() const;
+};
+
+using SplitView = std::ranges::split_view<EmptyView, EmptyView>;
+static_assert(sizeof(SplitView) == sizeof(std::ranges::__non_propagating_cache<std::ranges::subrange<int*>>));

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/adaptor.pass.cpp
new file mode 100644
index 0000000000000..cd12011daeab5
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/adaptor.pass.cpp
@@ -0,0 +1,126 @@
+//===----------------------------------------------------------------------===//
+//
+// 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::split
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <string_view>
+#include <utility>
+
+#include "test_iterators.h"
+
+template <class View, class T>
+concept CanBePiped = requires (View&& view, T&& t) {
+  { std::forward<View>(view) | std::forward<T>(t) };
+};
+
+struct SomeView : std::ranges::view_base {
+  const std::string_view* v_;
+  constexpr SomeView(const std::string_view& v) : v_(&v) {}
+  constexpr auto begin() const { return v_->begin(); }
+  constexpr auto end() const { return v_->end(); }
+};
+
+struct NotAView { };
+
+static_assert(!std::is_invocable_v<decltype(std::views::split)>);
+static_assert(!std::is_invocable_v<decltype(std::views::split), SomeView, NotAView>);
+static_assert(!std::is_invocable_v<decltype(std::views::split), NotAView, SomeView>);
+static_assert( std::is_invocable_v<decltype(std::views::split), SomeView, SomeView>);
+
+static_assert( CanBePiped<SomeView&,    decltype(std::views::split)>);
+static_assert( CanBePiped<char(&)[10],  decltype(std::views::split)>);
+static_assert(!CanBePiped<char(&&)[10], decltype(std::views::split)>);
+static_assert(!CanBePiped<NotAView,     decltype(std::views::split)>);
+
+static_assert(std::same_as<decltype(std::views::split), decltype(std::ranges::views::split)>);
+
+constexpr bool test() {
+  std::string_view input = "abc";
+  std::string_view sep = "a";
+
+  // Test that `std::views::split` is a range adaptor.
+
+  // Test `views::split(input, sep)`.
+  {
+    SomeView view(input);
+
+    using Result = std::ranges::split_view<SomeView, std::string_view>;
+    std::same_as<Result> decltype(auto) result = std::views::split(view, sep);
+    assert(result.base().begin() == input.begin());
+    assert(result.base().end() == input.end());
+  }
+
+  // Test `views::split(sep)(input)`.
+  {
+    SomeView view(input);
+
+    using Result = std::ranges::split_view<SomeView, std::string_view>;
+    std::same_as<Result> decltype(auto) result = std::views::split(sep)(view);
+    assert(result.base().begin() == input.begin());
+    assert(result.base().end() == input.end());
+  }
+
+  // Test `view | views::split`.
+  {
+    SomeView view(input);
+
+    using Result = std::ranges::split_view<SomeView, std::string_view>;
+    std::same_as<Result> decltype(auto) result = view | std::views::split(sep);
+    assert(result.base().begin() == input.begin());
+    assert(result.base().end() == input.end());
+  }
+
+  // Test `adaptor | views::split`.
+  {
+    SomeView view(input);
+    auto f = [](char c) { return c; };
+    auto partial = std::views::transform(f) | std::views::split(sep);
+
+    using Result = std::ranges::split_view<std::ranges::transform_view<SomeView, decltype(f)>, std::string_view>;
+    std::same_as<Result> decltype(auto) result = partial(view);
+    assert(result.base().base().begin() == input.begin());
+    assert(result.base().base().end() == input.end());
+  }
+
+  // Test `views::split | adaptor`.
+  {
+    SomeView view(input);
+    auto f = [](auto v) { return v; };
+    auto partial = std::views::split(sep) | std::views::transform(f);
+
+    using Result = std::ranges::transform_view<std::ranges::split_view<SomeView, std::string_view>, decltype(f)>;
+    std::same_as<Result> decltype(auto) result = partial(view);
+    assert(result.base().base().begin() == input.begin());
+    assert(result.base().base().end() == input.end());
+  }
+
+  // Test that one can call `std::views::split` with arbitrary stuff, as long as we
+  // don't try to actually complete the call by passing it a range.
+  //
+  // That makes no sense and we can't do anything with the result, but it's valid.
+  {
+    struct X { };
+    [[maybe_unused]] auto partial = std::views::split(X{});
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/base.pass.cpp
new file mode 100644
index 0000000000000..56ac55cfb54f8
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/base.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
+
+// constexpr V base() const & requires copy_constructible<V> { return base_; }
+// constexpr V base() && { return std::move(base_); }
+
+#include <cassert>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+
+#include "MoveOnly.h"
+
+struct View : std::ranges::view_base {
+  int i;
+  int* begin() const;
+  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::split_view<View, View> const&>);
+static_assert(HasBase<std::ranges::split_view<View, View>&&>);
+
+static_assert(!HasBase<std::ranges::split_view<MoveOnlyView, View> const&>);
+static_assert(HasBase<std::ranges::split_view<MoveOnlyView, View>&&>);
+
+constexpr bool test() {
+  // const &
+  {
+    const std::ranges::split_view<View, View> sv{View{{}, 5}, View{{}, 0}};
+    std::same_as<View> decltype(auto) v = sv.base();
+    assert(v.i == 5);
+  }
+
+  // &
+  {
+    std::ranges::split_view<View, View> sv{View{{}, 5}, View{{}, 0}};
+    std::same_as<View> decltype(auto) v = sv.base();
+    assert(v.i == 5);
+  }
+
+  // &&
+  {
+    std::ranges::split_view<View, View> sv{View{{}, 5}, View{{}, 0}};
+    std::same_as<View> decltype(auto) v = std::move(sv).base();
+    assert(v.i == 5);
+  }
+
+  // const &&
+  {
+    std::ranges::split_view<View, View> sv{View{{}, 5}, View{{}, 0}};
+    std::same_as<View> decltype(auto) v = std::move(sv).base();
+    assert(v.i == 5);
+  }
+
+  // move only
+  {
+    std::ranges::split_view<MoveOnlyView, View> sv{MoveOnlyView{{}, 5}, View{{}, 0}};
+    std::same_as<MoveOnlyView> decltype(auto) v = std::move(sv).base();
+    assert(v.mo.get() == 5);
+  }
+
+  // LWG 3590 split_view::base() const & is overconstrained
+  {
+    struct CopyCtorButNotAssignable : std::ranges::view_base {
+      int i;
+      constexpr CopyCtorButNotAssignable(int ii) : i(ii) {}
+      constexpr CopyCtorButNotAssignable(const CopyCtorButNotAssignable&)            = default;
+      constexpr CopyCtorButNotAssignable(CopyCtorButNotAssignable&&)                 = default;
+      constexpr CopyCtorButNotAssignable& operator=(CopyCtorButNotAssignable&&)      = default;
+      constexpr CopyCtorButNotAssignable& operator=(const CopyCtorButNotAssignable&) = delete;
+      constexpr int* begin() const { return nullptr; }
+      constexpr int* end() const { return nullptr; }
+    };
+    static_assert(std::copy_constructible<CopyCtorButNotAssignable>);
+    static_assert(!std::copyable<CopyCtorButNotAssignable>);
+    const std::ranges::split_view<CopyCtorButNotAssignable, CopyCtorButNotAssignable> sv{
+        CopyCtorButNotAssignable{5}, CopyCtorButNotAssignable{5}};
+    std::same_as<CopyCtorButNotAssignable> decltype(auto) v = sv.base();
+    assert(v.i == 5);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/begin.pass.cpp
new file mode 100644
index 0000000000000..15df4d0da65c1
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/begin.pass.cpp
@@ -0,0 +1,173 @@
+//===----------------------------------------------------------------------===//
+//
+// 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();
+
+#include <array>
+#include <cassert>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+
+#include "test_iterators.h"
+
+struct View : std::ranges::view_base {
+  int* begin() const;
+  int* end() const;
+};
+
+// Test that begin is not const
+template <class T>
+concept HasBegin = requires(T t) { t.begin(); };
+
+static_assert(HasBegin<std::ranges::split_view<View, View>>);
+static_assert(!HasBegin<const std::ranges::split_view<View, View>>);
+
+template <template <class> class MakeIter>
+constexpr void testOne() {
+  constexpr auto make_subrange = []<class T, std::size_t N>(T(&buffer)[N]) {
+    using Iter = MakeIter<T*>;
+    using Sent = sentinel_wrapper<Iter>;
+    return std::ranges::subrange<Iter, Sent>{Iter{buffer}, Sent{Iter{buffer + N}}};
+  };
+
+  using Iter  = MakeIter<int*>;
+  using Sent  = sentinel_wrapper<Iter>;
+  using Range = std::ranges::subrange<Iter, Sent>;
+
+  // empty view
+  {
+    std::array<int, 0> a;
+    Range range{Iter{a.data()}, Sent{Iter{a.data() + a.size()}}};
+    std::ranges::split_view sv{std::move(range), 1};
+    auto it         = sv.begin();
+    auto firstRange = *it;
+    assert(firstRange.begin() == range.begin());
+    assert(firstRange.end() == range.end());
+  }
+
+  // empty pattern
+  {
+    int buffer[] = {1, 2, 3};
+    auto range   = make_subrange(buffer);
+    std::ranges::split_view sv{std::move(range), std::views::empty<int>};
+    auto it         = sv.begin();
+    auto firstRange = *it;
+    assert(firstRange.begin() == range.begin());
+    assert(firstRange.end() == std::next(range.begin()));
+  }
+
+  // empty view and empty pattern
+  {
+    std::array<int, 0> a;
+    Range range{Iter{a.data()}, Sent{Iter{a.data() + a.size()}}};
+    std::ranges::split_view sv{std::move(range), std::views::empty<int>};
+    auto it         = sv.begin();
+    auto firstRange = *it;
+    assert(firstRange.begin() == range.begin());
+    assert(firstRange.end() == range.end());
+  }
+
+  // pattern found at the beginning
+  {
+    int buffer[]  = {1, 2, 3};
+    auto range    = make_subrange(buffer);
+    int pattern[] = {1, 2};
+    std::ranges::split_view sv{range, pattern};
+
+    auto it         = sv.begin();
+    auto firstRange = *it;
+    assert(firstRange.begin() == range.begin());
+    assert(firstRange.end() == range.begin());
+  }
+
+  // pattern found in the middle
+  {
+    int buffer[]  = {1, 2, 3, 4};
+    auto range    = make_subrange(buffer);
+    int pattern[] = {2, 3};
+    std::ranges::split_view sv{range, pattern};
+
+    auto it         = sv.begin();
+    auto firstRange = *it;
+    assert(firstRange.begin() == range.begin());
+    assert(firstRange.end() == std::next(range.begin()));
+  }
+
+  // pattern found at the end
+  {
+    int buffer[]  = {1, 2, 3};
+    auto range    = make_subrange(buffer);
+    int pattern[] = {2, 3};
+    std::ranges::split_view sv{range, pattern};
+
+    auto it         = sv.begin();
+    auto firstRange = *it;
+    assert(firstRange.begin() == range.begin());
+    assert(firstRange.end() == std::next(range.begin()));
+  }
+
+  // pattern not found
+  {
+    int buffer[]  = {1, 2, 3};
+    auto range    = make_subrange(buffer);
+    int pattern[] = {1, 3};
+    std::ranges::split_view sv{range, pattern};
+
+    auto it         = sv.begin();
+    auto firstRange = *it;
+    assert(firstRange.begin() == range.begin());
+    assert(firstRange.end() == range.end());
+  }
+
+  // Make sure that we cache the result of begin() on subsequent calls
+  {
+    struct Foo {
+      int& equalsCalledTimes;
+
+      constexpr bool operator==(const Foo&) const {
+        ++equalsCalledTimes;
+        return true;
+      }
+    };
+
+    int equalsCalledTimes = 0;
+    Foo buffer[]          = {Foo{equalsCalledTimes}, Foo{equalsCalledTimes}};
+    auto range            = make_subrange(buffer);
+
+    std::ranges::split_view sv{range, Foo{equalsCalledTimes}};
+
+    assert(equalsCalledTimes == 0);
+
+    [[maybe_unused]] auto it1       = sv.begin();
+    auto calledTimesAfterFirstBegin = equalsCalledTimes;
+    assert(calledTimesAfterFirstBegin != 0);
+
+    for (int i = 0; i < 10; ++i) {
+      [[maybe_unused]] auto it2 = sv.begin();
+      assert(equalsCalledTimes == calledTimesAfterFirstBegin);
+    }
+  }
+}
+
+constexpr bool test() {
+  testOne<forward_iterator>();
+  testOne<bidirectional_iterator>();
+  testOne<random_access_iterator>();
+  testOne<contiguous_iterator>();
+  testOne<std::type_identity_t>();
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/constraints.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/constraints.compile.pass.cpp
new file mode 100644
index 0000000000000..66a491f59b256
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/constraints.compile.pass.cpp
@@ -0,0 +1,48 @@
+//===----------------------------------------------------------------------===//
+//
+// 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<forward_range V, forward_range Pattern>
+//   requires view<V> && view<Pattern> &&
+//            indirectly_comparable<iterator_t<V>, iterator_t<Pattern>, ranges::equal_to>
+// class split_view;
+
+#include <ranges>
+
+#include "test_macros.h"
+#include "test_iterators.h"
+
+template <class It>
+using Range = std::ranges::subrange<It, sentinel_wrapper<It>>;
+
+template <class View, class Pattern>
+concept HasSplitView = requires { typename std::ranges::split_view<View, Pattern>; };
+
+static_assert(HasSplitView<Range<int*>, Range<int*>>);
+
+// !forward_range<V>
+static_assert(!HasSplitView<Range<cpp20_input_iterator<int*>>, Range<int*>>);
+
+// !forward_range<Pattern>
+static_assert(!HasSplitView<Range<int*>, Range<cpp20_input_iterator<int*>>>);
+
+struct NotAView {
+  int* begin() const;
+  int* end() const;
+};
+
+// !view<V>
+static_assert(!HasSplitView<NotAView, Range<int*>>);
+
+// !view<Pattern>
+static_assert(!HasSplitView<Range<int*>, NotAView>);
+
+// indirectly_comparable<iterator_t<V>, iterator_t<Pattern>, ranges::equal_to
+struct Foo {};
+static_assert(!HasSplitView<Range<int*>, Range<Foo*>>);

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/ctad.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/ctad.compile.pass.cpp
new file mode 100644
index 0000000000000..90d49e49279c3
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/ctad.compile.pass.cpp
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 R, class P>
+// split_view(R&&, P&&) -> split_view<views::all_t<R>, views::all_t<P>>;
+//
+// template <input_range R>
+// split_view(R&&, range_value_t<R>) -> split_view<views::all_t<R>, single_view<range_value_t<R>>>;
+
+#include <concepts>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+
+struct Container {
+  int* begin() const;
+  int* end() const;
+};
+
+struct View : std::ranges::view_base {
+  int* begin() const;
+  int* end() const;
+};
+
+template <class I1, class I2, class ExpectedView, class ExpectedPattern>
+constexpr void test() {
+  static_assert(std::is_same_v<decltype(std::ranges::split_view(std::declval<I1>(), std::declval<I2>())),
+                               std::ranges::split_view<ExpectedView, ExpectedPattern>>);
+}
+
+constexpr void testCtad() {
+  // (Range, Pattern)
+  test<View, View, View, View>();
+  test<Container&, Container&, std::ranges::ref_view<Container>, std::ranges::ref_view<Container>>();
+  test<Container&&, Container&&, std::ranges::owning_view<Container>, std::ranges::owning_view<Container>>();
+
+  // (Range, RangeElement)
+  test<Container&, int, std::ranges::ref_view<Container>, std::ranges::single_view<int>>();
+  test<View, int, View, std::ranges::single_view<int>>();
+
+  // (Range, RangeElement) with implicit conversion.
+  test<Container&, bool, std::ranges::ref_view<Container>, std::ranges::single_view<int>>();
+  test<View, bool, View, std::ranges::single_view<int>>();
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..aff0740c966c1
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/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
+
+// constexpr split_view() requires
+//   default_initializable<V> && default_initializable<Pattern> = default;
+
+#include <cassert>
+#include <ranges>
+#include <type_traits>
+
+template <bool DefaultInitializable>
+struct View : std::ranges::view_base {
+  int i = 42;
+  constexpr explicit View()
+    requires DefaultInitializable
+  = default;
+  int* begin() const;
+  int* end() const;
+};
+
+// clang-format off
+static_assert( std::is_default_constructible_v<std::ranges::split_view<View<true >, View<true >>>);
+static_assert(!std::is_default_constructible_v<std::ranges::split_view<View<false>, View<true >>>);
+static_assert(!std::is_default_constructible_v<std::ranges::split_view<View<true >, View<false>>>);
+static_assert(!std::is_default_constructible_v<std::ranges::split_view<View<false>, View<false>>>);
+// clang-format on
+
+constexpr bool test() {
+  {
+    std::ranges::split_view<View<true>, View<true>> sv = {};
+    assert(sv.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.split/ctor.range.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/ctor.range.pass.cpp
new file mode 100644
index 0000000000000..605e3d544b2d8
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/ctor.range.pass.cpp
@@ -0,0 +1,139 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 <input_range Range>
+//   requires constructible_from<View, views::all_t<Range>> &&
+//             constructible_from<Pattern, single_view<range_value_t<Range>>>
+// constexpr split_view(Range&& r, range_value_t<Range> e);
+
+#include <algorithm>
+#include <cassert>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <utility>
+
+struct Counting {
+  int* times_copied = nullptr;
+  int* times_moved  = nullptr;
+
+  constexpr Counting(int& copies_ctr, int& moves_ctr) : times_copied(&copies_ctr), times_moved(&moves_ctr) {}
+
+  constexpr Counting(const Counting& rhs) : times_copied(rhs.times_copied), times_moved(rhs.times_moved) {
+    ++(*times_copied);
+  }
+  constexpr Counting(Counting&& rhs) : times_copied(rhs.times_copied), times_moved(rhs.times_moved) {
+    ++(*times_moved);
+  }
+
+  constexpr Counting& operator=(const Counting&) = default;
+  constexpr Counting& operator=(Counting&&)      = default;
+};
+
+struct ElementWithCounting : Counting {
+  using Counting::Counting;
+
+  constexpr bool operator==(const ElementWithCounting&) const { return true; }
+};
+
+struct RangeWithCounting : Counting {
+  using Counting::Counting;
+
+  constexpr const ElementWithCounting* begin() const { return nullptr; }
+  constexpr const ElementWithCounting* end() const { return nullptr; }
+
+  constexpr bool operator==(const RangeWithCounting&) const { return true; }
+};
+static_assert(std::ranges::forward_range<RangeWithCounting>);
+static_assert(!std::ranges::view<RangeWithCounting>);
+
+struct StrView : std::ranges::view_base {
+  std::string buffer_;
+
+  template <std::ranges::range R>
+  constexpr StrView(R&& r) : buffer_(std::ranges::begin(r), std::ranges::end(r)) {}
+  constexpr const char* begin() const { return buffer_.data(); }
+  constexpr const char* end() const { return buffer_.data() + buffer_.size(); }
+  constexpr bool operator==(const StrView& rhs) const { return buffer_ == rhs.buffer_; }
+};
+static_assert(std::ranges::random_access_range<StrView>);
+static_assert(std::ranges::view<StrView>);
+static_assert(std::is_copy_constructible_v<StrView>);
+
+constexpr bool test() {
+  {
+    using V = std::ranges::split_view<StrView, StrView>;
+
+    // Calling the constructor with `(std::string, range_value_t)`.
+    {
+      std::string input("abc def");
+      V v(input, ' ');
+      assert(v.base() == input);
+      assert(std::ranges::equal(*v.begin(), std::string_view("abc")));
+    }
+
+    // Calling the constructor with `(StrView, range_value_t)`.
+    {
+      StrView input("abc def");
+      V v(input, ' ');
+      assert(v.base() == input);
+      assert(std::ranges::equal(*v.begin(), std::string_view("abc")));
+    }
+
+    struct Empty {};
+    static_assert(!std::is_constructible_v<V, Empty, std::string_view>);
+    static_assert(!std::is_constructible_v<V, std::string_view, Empty>);
+  }
+
+  // Make sure the arguments are moved, not copied.
+  {
+    using Range   = RangeWithCounting;
+    using Element = ElementWithCounting;
+    using Pattern = std::ranges::single_view<Element>;
+
+    // Arguments are lvalues.
+    {
+      using View = std::ranges::ref_view<Range>;
+
+      int range_copied = 0, range_moved = 0, element_copied = 0, element_moved = 0;
+      Range range(range_copied, range_moved);
+      Element element(element_copied, element_moved);
+
+      std::ranges::split_view<View, Pattern> v(range, element);
+      assert(range_copied == 0);   // `ref_view` does neither copy...
+      assert(range_moved == 0);    // ...nor move the element.
+      assert(element_copied == 1); // The element is copied into the argument...
+      assert(element_moved == 1);  // ...and moved into the member variable.
+    }
+
+    // Arguments are rvalues.
+    {
+      using View = std::ranges::owning_view<Range>;
+
+      int range_copied = 0, range_moved = 0, element_copied = 0, element_moved = 0;
+      std::ranges::split_view<View, Pattern> v(
+          Range(range_copied, range_moved), Element(element_copied, element_moved));
+      assert(range_copied == 0);
+      assert(range_moved == 1); // `owning_view` moves the given argument.
+      assert(element_copied == 0);
+      assert(element_moved == 1);
+    }
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/ctor.view.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/ctor.view.pass.cpp
new file mode 100644
index 0000000000000..ad206ee5ed751
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/ctor.view.pass.cpp
@@ -0,0 +1,86 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// constexpr split_view(View base, Pattern pattern);
+
+#include <algorithm>
+#include <cassert>
+#include <ranges>
+#include <string_view>
+#include <utility>
+
+struct ViewWithCounting : std::ranges::view_base {
+  int* times_copied = nullptr;
+  int* times_moved  = nullptr;
+
+  constexpr ViewWithCounting(int& copies_ctr, int& moves_ctr) : times_copied(&copies_ctr), times_moved(&moves_ctr) {}
+
+  constexpr ViewWithCounting(const ViewWithCounting& rhs)
+      : times_copied(rhs.times_copied), times_moved(rhs.times_moved) {
+    ++(*times_copied);
+  }
+  constexpr ViewWithCounting(ViewWithCounting&& rhs) : times_copied(rhs.times_copied), times_moved(rhs.times_moved) {
+    ++(*times_moved);
+  }
+
+  constexpr const char* begin() const { return nullptr; }
+  constexpr const char* end() const { return nullptr; }
+
+  constexpr ViewWithCounting& operator=(const ViewWithCounting&) = default;
+  constexpr ViewWithCounting& operator=(ViewWithCounting&&)      = default;
+  constexpr bool operator==(const ViewWithCounting&) const { return true; }
+};
+
+constexpr bool test() {
+  {
+    std::string_view input = "abc def";
+    std::ranges::lazy_split_view<std::string_view, std::string_view> v(input, " ");
+    assert(v.base() == input);
+    assert(std::ranges::equal(*v.begin(), std::string_view{"abc"}));
+  }
+
+  // Make sure the arguments are moved, not copied.
+  {
+    using View    = ViewWithCounting;
+    using Pattern = ViewWithCounting;
+
+    // Arguments are lvalues.
+    {
+      int view_copied = 0, view_moved = 0, pattern_copied = 0, pattern_moved = 0;
+      View view(view_copied, view_moved);
+      Pattern pattern(pattern_copied, pattern_moved);
+
+      std::ranges::split_view<View, Pattern> v(view, pattern);
+      assert(view_copied == 1); // The local variable is copied into the argument.
+      assert(view_moved == 1);
+      assert(pattern_copied == 1);
+      assert(pattern_moved == 1);
+    }
+
+    // Arguments are rvalues.
+    {
+      int view_copied = 0, view_moved = 0, pattern_copied = 0, pattern_moved = 0;
+      std::ranges::split_view<View, Pattern> v(View(view_copied, view_moved), Pattern(pattern_copied, pattern_moved));
+      assert(view_copied == 0);
+      assert(view_moved == 1);
+      assert(pattern_copied == 0);
+      assert(pattern_moved == 1);
+    }
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/end.pass.cpp
new file mode 100644
index 0000000000000..6c8aeceff9887
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/end.pass.cpp
@@ -0,0 +1,70 @@
+//===----------------------------------------------------------------------===//
+//
+// 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();
+
+#include <cassert>
+#include <ranges>
+#include <type_traits>
+#include <utility>
+
+#include "test_iterators.h"
+
+struct View : std::ranges::view_base {
+  int* begin() const;
+  int* end() const;
+};
+
+// Test that end is not const
+template <class T>
+concept HasEnd = requires(T t) { t.end(); };
+
+static_assert(HasEnd<std::ranges::split_view<View, View>>);
+static_assert(!HasEnd<const std::ranges::split_view<View, View>>);
+
+constexpr bool test() {
+  // return iterator
+  {
+    int buffer[]   = {1, 2, -1, 4, 5, 6, 5, 4, -1, 2, 1};
+    auto inputView = std::views::all(buffer);
+    static_assert(std::ranges::common_range<decltype(inputView)>);
+
+    std::ranges::split_view sv(buffer, -1);
+    using SplitIter                           = std::ranges::iterator_t<decltype(sv)>;
+    std::same_as<SplitIter> decltype(auto) sentinel = sv.end();
+    assert(sentinel.base() == buffer + 11);
+  }
+
+  // return sentinel
+  {
+    using Iter   = int*;
+    using Sent   = sentinel_wrapper<Iter>;
+    using Range  = std::ranges::subrange<Iter, Sent>;
+    int buffer[] = {1, 2, -1, 4, 5, 6, 5, 4, -1, 2, 1};
+    Range range  = {buffer, Sent{buffer + 11}};
+    static_assert(!std::ranges::common_range<Range>);
+
+    std::ranges::split_view sv(range, -1);
+    auto sentinel = sv.end();
+
+    using SplitIter = std::ranges::iterator_t<decltype(sv)>;
+    static_assert(!std::same_as<decltype(sentinel), SplitIter>);
+
+    assert(std::next(sv.begin(), 3) == sentinel);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/general.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/general.pass.cpp
new file mode 100644
index 0000000000000..8684e3b6cd9e7
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/general.pass.cpp
@@ -0,0 +1,395 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 split_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.
+
+// These test check the output `split_view` produces for a variety of inputs, including many corner cases, with no
+// restrictions on which member functions can be called.
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <map>
+#include <ranges>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include "test_macros.h"
+
+template <std::ranges::view View, std::ranges::range Expected>
+constexpr bool is_equal(View& view, const Expected& expected) {
+  return std::ranges::equal(view, expected, std::ranges::equal);
+}
+
+template <class T, class Separator, class U, size_t M>
+constexpr bool test_function_call(T&& input, Separator&& separator, std::array<U, M> expected) {
+  std::ranges::split_view v(input, separator);
+  return is_equal(v, expected);
+}
+
+template <class T, class Separator, class U, size_t M>
+constexpr bool test_with_piping(T&& input, Separator&& separator, std::array<U, M> expected) {
+  auto expected_it = expected.begin();
+  for (auto e : input | std::ranges::views::split(separator)) {
+    if (expected_it == expected.end())
+      return false;
+    if (!std::ranges::equal(e, *expected_it))
+      return false;
+
+    ++expected_it;
+  }
+
+  return expected_it == expected.end();
+}
+
+constexpr bool test_l_r_values() {
+  using namespace std::string_view_literals;
+
+  // Both lvalues and rvalues can be used as input.
+  {
+    // Lvalues.
+    {
+      auto input = "abc"sv;
+      auto sep   = " "sv;
+      [[maybe_unused]] std::ranges::split_view v(input, sep);
+    }
+
+    // Const lvalues.
+    {
+      const auto input = "abc"sv;
+      const auto sep   = " "sv;
+      [[maybe_unused]] std::ranges::split_view v(input, sep);
+    }
+
+    // Rvalues.
+    {
+      auto input = "abc"sv;
+      auto sep   = " "sv;
+      [[maybe_unused]] std::ranges::split_view v(std::move(input), std::move(sep));
+    }
+
+    // Const rvalues.
+    {
+      const auto input = "abc"sv;
+      const auto sep   = " "sv;
+      [[maybe_unused]] std::ranges::split_view v(std::move(input), std::move(sep));
+    }
+  }
+
+  return true;
+}
+
+constexpr bool test_string_literal_separator() {
+  using namespace std::string_view_literals;
+
+  // Splitting works as expected when the separator is a single character literal.
+  {
+    std::ranges::split_view v("abc def"sv, ' ');
+    assert(is_equal(v, std::array{"abc"sv, "def"sv}));
+  }
+
+  // Counterintuitively, a seemingly equivalent separator expressed as a string literal doesn't match anything. This is
+  // because of the implicit terminating null in the literal.
+  {
+    std::ranges::split_view v("abc def"sv, " ");
+    assert(is_equal(v, std::array{"abc def"sv}));
+  }
+
+  // To illustrate the previous point further, the separator is actually a two-character string literal: `{' ', '\0'}`.
+  // Should the input string contain that two-character sequence, the separator would match.
+  {
+    std::ranges::split_view v("abc \0def"sv, " ");
+    assert(is_equal(v, std::array{"abc"sv, "def"sv}));
+  }
+
+  return true;
+}
+
+// Make sure that a string literal and a `string_view` produce the same results (which isn't always the case, see
+// below).
+template <class T>
+constexpr std::string_view sv(T&& str) {
+  return std::string_view(str);
+};
+
+template <class T, class Separator, class U, size_t M>
+constexpr void test_one(T&& input, Separator&& separator, std::array<U, M> expected) {
+  assert(test_function_call(input, separator, expected));
+  assert(test_with_piping(input, separator, expected));
+}
+
+constexpr bool test_string_literals() {
+  // These tests show characteristic examples of how using string literals with `split_view` produces unexpected
+  // results due to the implicit terminating null that is treated as part of the range.
+
+  using namespace std::string_view_literals;
+
+  char short_sep = ' ';
+  auto long_sep  = "12"sv;
+
+  // When splitting a string literal, only the last segment will be null-terminated (getting the terminating null from
+  // the original range).
+  {
+    std::array expected = {"abc"sv, std::string_view("def", sizeof("def"))};
+
+    assert(test_function_call("abc def", short_sep, expected));
+    assert(test_with_piping("abc def", short_sep, expected));
+    assert(test_function_call("abc12def", long_sep, expected));
+    assert(test_with_piping("abc12def", long_sep, expected));
+  }
+
+  // Empty string.
+  {
+    // Because an empty string literal contains an implicit terminating null, the output will contain one segment.
+    std::array expected = {std::string_view("", 1)};
+
+    assert(test_function_call("", short_sep, expected));
+    assert(test_with_piping("", short_sep, expected));
+    assert(test_function_call("", long_sep, expected));
+    assert(test_with_piping("", long_sep, expected));
+  }
+
+  // Terminating null in the separator -- the character literal `' '` and the seemingly equivalent string literal `" "`
+  // are treated 
diff erently due to the presence of an implicit `\0` in the latter.
+  {
+    const char input[]          = "abc def";
+    std::array expected_unsplit = {std::string_view(input, sizeof(input))};
+    std::array expected_split   = {"abc"sv, std::string_view("def", sizeof("def"))};
+
+    assert(test_function_call(input, " ", expected_unsplit));
+    assert(test_function_call("abc \0def", " ", expected_split));
+    // Note: string literals don't work with piping because arrays decay to pointers, and pointers don't model `range`.
+  }
+
+  // Empty separator.
+  {
+    auto empty_sep      = ""sv;
+    std::array expected = {"a"sv, "b"sv, "c"sv, "\0"sv};
+
+    assert(test_function_call("abc", empty_sep, expected));
+    assert(test_with_piping("abc", empty_sep, expected));
+  }
+
+  return true;
+}
+
+bool test_nontrivial_characters() {
+  // Try a deliberately heavyweight "character" type to see if it triggers any corner cases.
+
+  using Map = std::map<std::string, int>;
+  using Vec = std::vector<Map>;
+
+  Map sep = {{"yyy", 999}};
+  Map m1  = {
+      {"a", 1},
+      {"bc", 2},
+  };
+  Map m2 = {
+      {"def", 3},
+  };
+  Map m3 = {
+      {"g", 4},
+      {"hijk", 5},
+  };
+
+  Vec expected1 = {m1, m2};
+  Vec expected2 = {m3};
+
+  std::ranges::split_view v(Vec{m1, m2, sep, m3}, sep);
+
+  // Segment 1: {m1, m2}
+  auto outer = v.begin();
+  assert(outer != v.end());
+  auto inner = (*outer).begin();
+  assert(*inner++ == m1);
+  assert(*inner++ == m2);
+  assert(inner == (*outer).end());
+
+  // Segment 2: {m3}
+  ++outer;
+  assert(outer != v.end());
+  inner = (*outer).begin();
+  assert(*inner++ == m3);
+  assert(inner == (*outer).end());
+
+  ++outer;
+  assert(outer == v.end());
+
+  return true;
+}
+
+constexpr bool main_test() {
+  using namespace std::string_view_literals;
+
+  char short_sep = ' ';
+  auto long_sep  = "12"sv;
+
+  // One separator.
+  {
+    std::array expected = {"abc"sv, "def"sv};
+    test_one("abc def"sv, short_sep, expected);
+    test_one("abc12def"sv, long_sep, expected);
+  }
+
+  // Several separators in a row.
+  {
+    std::array expected = {"abc"sv, ""sv, ""sv, ""sv, "def"sv};
+    test_one("abc    def"sv, short_sep, expected);
+    test_one("abc12121212def"sv, long_sep, expected);
+  }
+
+  // Trailing separator.
+  {
+    std::array expected = {"abc"sv, "def"sv, ""sv};
+    test_one("abc def "sv, short_sep, expected);
+    test_one("abc12def12"sv, long_sep, expected);
+  }
+
+  // Leading separator.
+  {
+    std::array expected = {""sv, "abc"sv, "def"sv};
+    test_one(" abc def"sv, short_sep, expected);
+    test_one("12abc12def"sv, long_sep, expected);
+  }
+
+  // No separator.
+  {
+    std::array expected = {"abc"sv};
+    test_one("abc"sv, short_sep, expected);
+    test_one("abc"sv, long_sep, expected);
+  }
+
+  // Input consisting of a single separator.
+  {
+    std::array expected = {""sv, ""sv};
+    test_one(" "sv, short_sep, expected);
+    test_one("12"sv, long_sep, expected);
+  }
+
+  // Input consisting of only separators.
+  {
+    std::array expected = {""sv, ""sv, ""sv, ""sv};
+    test_one("   "sv, short_sep, expected);
+    test_one("121212"sv, long_sep, expected);
+  }
+
+  // The separator and the string use the same character only.
+  {
+    auto overlapping_sep = "aaa"sv;
+    std::array expected  = {""sv, "aa"sv};
+    test_one("aaaaa"sv, overlapping_sep, expected);
+  }
+
+  // Many redundant separators.
+  {
+    std::array expected = {""sv, ""sv, "abc"sv, ""sv, ""sv, "def"sv, ""sv, ""sv};
+    test_one("  abc   def  "sv, short_sep, expected);
+    test_one("1212abc121212def1212"sv, long_sep, expected);
+  }
+
+  // Separators after every character.
+  {
+    std::array expected = {""sv, "a"sv, "b"sv, "c"sv, ""sv};
+    test_one(" a b c "sv, short_sep, expected);
+    test_one("12a12b12c12"sv, long_sep, expected);
+  }
+
+  // Overlap between the separator and the string (see https://wg21.link/lwg3505).
+  {
+    auto overlapping_sep = "ab"sv;
+    std::array expected  = {"a"sv, "aa"sv, ""sv, "b"sv};
+    test_one("aabaaababb"sv, overlapping_sep, expected);
+  }
+
+  // Empty input.
+  {
+    std::array<std::string_view, 0> expected = {};
+    test_one(""sv, short_sep, expected);
+    test_one(""sv, long_sep, expected);
+  }
+
+  // Empty separator.
+  {
+    auto empty_sep      = ""sv;
+    std::array expected = {"a"sv, "b"sv, "c"sv};
+    test_one("abc"sv, empty_sep, expected);
+    test_one("abc"sv, empty_sep, expected);
+  }
+
+  // Terminating null as a separator.
+  {
+    std::array expected = {"abc"sv, "def"sv};
+    test_one("abc\0def"sv, '\0', expected);
+    test_one("abc\0\0def"sv, "\0\0"sv, expected);
+  }
+
+  // Different character types.
+  {
+    // `char`.
+    test_function_call("abc def", ' ', std::array{"abc"sv, "def"sv});
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+    // `wchar_t`.
+    test_function_call(L"abc def", L' ', std::array{L"abc"sv, L"def"sv});
+#endif
+    // `char8_t`.
+    test_function_call(u8"abc def", u8' ', std::array{u8"abc"sv, u8"def"sv});
+    // `char16_t`.
+    test_function_call(u"abc def", u' ', std::array{u"abc"sv, u"def"sv});
+    // `char32_t`.
+    test_function_call(U"abc def", U' ', std::array{U"abc"sv, U"def"sv});
+  }
+
+  // Non-character input.
+  {
+    std::array expected = {std::array{1, 2, 3}, std::array{4, 5, 6}};
+    test_one(std::array{1, 2, 3, 0, 4, 5, 6}, 0, expected);
+    test_one(std::array{1, 2, 3, 0, 0, 0, 4, 5, 6}, std::array{0, 0, 0}, expected);
+  }
+
+  return true;
+}
+
+constexpr bool example_test() {
+  // example code in the spec
+  std::string str{"the quick brown fox"};
+  std::vector<std::string_view> result;
+  for (auto r : std::views::split(str, ' ')) {
+    result.emplace_back(r.begin(), r.end());
+  }
+  using namespace std::string_view_literals;
+  auto expected = {"the"sv, "quick"sv, "brown"sv, "fox"sv};
+  assert(std::ranges::equal(result, expected));
+
+  return true;
+}
+
+int main(int, char**) {
+  example_test();
+  static_assert(example_test());
+
+  test_string_literals();
+  static_assert(test_string_literals());
+
+  test_l_r_values();
+  static_assert(test_l_r_values());
+
+  test_string_literal_separator();
+  static_assert(test_string_literal_separator());
+
+  // Note: map is not `constexpr`, so this test is runtime-only.
+  test_nontrivial_characters();
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/iterator/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/base.pass.cpp
new file mode 100644
index 0000000000000..325189a0e521e
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/base.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 iterator_t<V> base() const;
+
+#include <cassert>
+#include <ranges>
+
+#include "../types.h"
+
+struct Iter : ForwardIterBase<Iter> {
+  int i;
+  constexpr Iter() = default;
+  constexpr Iter(int ii) : i(ii) {}
+};
+
+constexpr bool test() {
+  // base only has one const overload
+  using SplitView = std::ranges::split_view<std::ranges::subrange<Iter>, std::ranges::subrange<Iter>>;
+  using SplitIter = std::ranges::iterator_t<SplitView>;
+
+  // const &
+  {
+    SplitView sv;
+    const SplitIter it{sv, Iter{5}, {}};
+    std::same_as<Iter> decltype(auto) base = it.base();
+    assert(base.i == 5);
+  }
+
+  // &
+  {
+    SplitView sv;
+    SplitIter it{sv, Iter{5}, {}};
+    std::same_as<Iter> decltype(auto) base = it.base();
+    assert(base.i == 5);
+  }
+
+  // &&
+  {
+    SplitView sv;
+    SplitIter it{sv, Iter{5}, {}};
+    std::same_as<Iter> decltype(auto) base = std::move(it).base();
+    assert(base.i == 5);
+  }
+
+  // const &&
+  {
+    SplitView sv;
+    const SplitIter it{sv, Iter{5}, {}};
+    std::same_as<Iter> decltype(auto) base = std::move(it).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.split/iterator/ctor.base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/ctor.base.pass.cpp
new file mode 100644
index 0000000000000..20b3c19611bd0
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/ctor.base.pass.cpp
@@ -0,0 +1,53 @@
+//===----------------------------------------------------------------------===//
+//
+// 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(split_view& parent, iterator_t<V> current, subrange<iterator_t<V>> next);
+
+#include <cassert>
+#include <ranges>
+
+#include "../types.h"
+
+struct TracedMoveIter : ForwardIterBase<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 {
+  constexpr TracedMoveIter begin() const { return {}; }
+  constexpr TracedMoveIter end() const { return {}; }
+};
+
+constexpr bool test() {
+  using SplitView = std::ranges::split_view<TracedMoveView, TracedMoveView>;
+  using SplitIter = std::ranges::iterator_t<SplitView>;
+
+  SplitView sv{TracedMoveView{}, TracedMoveView{}};
+  SplitIter iter = {sv, sv.base().begin(), std::ranges::subrange<TracedMoveIter>{sv.base().begin(), sv.base().end()}};
+  assert(iter.base().moved);
+
+  auto subRange = *iter;
+  assert(subRange.begin().moved);
+  assert(subRange.end().moved);
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/iterator/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..72a6fe55c47ff
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/ctor.default.pass.cpp
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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() = default;
+
+#include <cassert>
+#include <ranges>
+
+#include "../types.h"
+
+struct PODIter : ForwardIterBase<PODIter> {
+  int i;
+};
+
+constexpr bool test() {
+  using SplitView = std::ranges::split_view<std::ranges::subrange<PODIter>, std::ranges::subrange<PODIter>>;
+  using SplitIter = std::ranges::iterator_t<SplitView>;
+  {
+    SplitIter iter;
+    assert(iter.base().i == 0); // PODIter has to be initialised to have value 0
+  }
+
+  {
+    SplitIter iter = {};        // explicit(false)
+    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.split/iterator/deref.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/deref.pass.cpp
new file mode 100644
index 0000000000000..721a1cc0da3d4
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/deref.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
+
+// constexpr value_type operator*() const;
+//   Effects: Equivalent to return {cur_, next_.begin()};
+
+#include <cassert>
+#include <ranges>
+
+#include "../types.h"
+
+struct Iter : ForwardIterBase<Iter> {
+  int i;
+  constexpr Iter() = default;
+  constexpr Iter(int ii) : i(ii) {}
+};
+
+constexpr bool test() {
+  using SplitView = std::ranges::split_view<std::ranges::subrange<Iter>, std::ranges::subrange<Iter>>;
+  using SplitIter = std::ranges::iterator_t<SplitView>;
+
+  {
+    SplitView sv;
+    Iter current{5};
+    std::ranges::subrange next{Iter{6}, Iter{7}};
+    const SplitIter it{sv, current, next};
+    std::same_as<std::ranges::subrange<Iter>> decltype(auto) value = *it;
+    assert(value.begin().i == 5);
+    assert(value.end().i == 6);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/iterator/equal.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/equal.pass.cpp
new file mode 100644
index 0000000000000..30888d1307188
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/equal.pass.cpp
@@ -0,0 +1,99 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// friend constexpr bool operator==(const iterator& x, const iterator& y);
+
+#include <algorithm>
+#include <cassert>
+#include <concepts>
+#include <ranges>
+
+#include "test_iterators.h"
+
+template <class Iter>
+constexpr void testOne() {
+  using Range     = std::ranges::subrange<Iter>;
+  using SplitView = std::ranges::split_view<Range, std::ranges::single_view<int>>;
+  static_assert(std::ranges::common_range<SplitView>);
+
+  {
+    // simple test
+    {
+      int buffer[] = {0, 1, 2, -1, 4, 5, 6};
+      Range input(Iter{buffer}, Iter{buffer + 7});
+      SplitView sv(input, -1);
+      auto b = sv.begin(), e = sv.end();
+
+      assert(b == b);
+      assert(!(b != b));
+
+      assert(e == e);
+      assert(!(e != e));
+
+      assert(!(b == e));
+      assert(b != e);
+
+      std::advance(b, 2);
+      assert(b == b);
+      assert(!(b != b));
+
+      assert(e == e);
+      assert(!(e != e));
+
+      assert(b == e);
+      assert(!(b != e));
+    }
+
+    // iterator at trailing empty position should not equal to end
+    {
+      int buffer[] = {0, 1, 2, -1};
+      Range input(Iter{buffer}, Iter{buffer + 4});
+      SplitView sv(input, -1);
+      auto b = sv.begin(), e = sv.end();
+
+      ++b; // cur points to end but trailing_empty is true
+
+      assert(b != e);
+      assert(!(b == e));
+
+      ++b;
+      assert(b == e);
+      assert(!(b != e));
+    }
+
+    // Default-constructed iterators compare equal.
+    {
+      int buffer[] = {0, 1, 2, -1, 4, 5, 6};
+      Range input(Iter{buffer}, Iter{buffer + 7});
+      std::ranges::split_view sv(buffer, -1);
+      using SplitIter = decltype(sv.begin());
+      SplitIter i1, i2;
+      assert(i1 == i2);
+      assert(!(i1 != i2));
+    }
+  }
+}
+
+constexpr bool test() {
+  testOne<forward_iterator<int*>>();
+  testOne<bidirectional_iterator<int*>>();
+  testOne<random_access_iterator<int*>>();
+  testOne<contiguous_iterator<int*>>();
+  testOne<int*>();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/iterator/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/increment.pass.cpp
new file mode 100644
index 0000000000000..56036dc1f4109
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/increment.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
+
+// constexpr iterator& operator++();
+// constexpr iterator operator++(int);
+
+#include <cassert>
+#include <ranges>
+
+#include "test_iterators.h"
+
+template <class Iter>
+constexpr void testOne() {
+  constexpr auto make_subrange = []<std::size_t N>(int(&buffer)[N]) {
+    return std::ranges::subrange<Iter>{Iter{buffer}, Iter{buffer + N}};
+  };
+
+  // next subrange does not reach the end
+  {
+    int buffer[] = {0, 1, 2, -1, 4, 5, -1, 7};
+    auto input   = make_subrange(buffer);
+    std::ranges::split_view sv(input, -1);
+    using SplitIter = std::ranges::iterator_t<decltype(sv)>;
+
+    // ++it
+    {
+      auto it = sv.begin();
+
+      decltype(auto) it1 = ++it;
+      static_assert(std::is_same_v<decltype(it1), SplitIter&>);
+      assert(&it1 == &it);
+
+      assert(base((*it).begin()) == buffer + 4);
+      assert(base((*it).end()) == buffer + 6);
+
+      ++it;
+      assert(base((*it).begin()) == buffer + 7);
+      assert(base((*it).end()) == buffer + 8);
+    }
+
+    // it++
+    {
+      auto it       = sv.begin();
+      auto original = it;
+
+      decltype(auto) it1 = it++;
+      static_assert(std::is_same_v<decltype(it1), SplitIter>);
+      assert(it1 == original);
+
+      assert(base((*it).begin()) == buffer + 4);
+      assert(base((*it).end()) == buffer + 6);
+
+      it++;
+      assert(base((*it).begin()) == buffer + 7);
+      assert(base((*it).end()) == buffer + 8);
+    }
+  }
+
+  // next's begin is the end
+  {
+    int buffer[] = {0, 1, 2};
+    auto input   = make_subrange(buffer);
+    std::ranges::split_view sv(input, -1);
+    using SplitIter = std::ranges::iterator_t<decltype(sv)>;
+
+    // ++it
+    {
+      auto it = sv.begin();
+
+      decltype(auto) it1 = ++it; // trailing_empty is false
+      static_assert(std::is_same_v<decltype(it1), SplitIter&>);
+      assert(&it1 == &it);
+
+      assert(it == sv.end());
+    }
+
+    // it++
+    {
+      auto it       = sv.begin();
+      auto original = it;
+
+      decltype(auto) it1 = it++; // trailing_empty is false
+      static_assert(std::is_same_v<decltype(it1), SplitIter>);
+      assert(it1 == original);
+
+      assert(it == sv.end());
+    }
+  }
+
+  // next's end is the end
+  {
+    int buffer[] = {0, 1, 2, -1};
+    auto input   = make_subrange(buffer);
+    std::ranges::split_view sv(input, -1);
+    using SplitIter = std::ranges::iterator_t<decltype(sv)>;
+
+    // ++it
+    {
+      auto it = sv.begin();
+
+      decltype(auto) it1 = ++it; // trailing_empty is true
+      static_assert(std::is_same_v<decltype(it1), SplitIter&>);
+      assert(&it1 == &it);
+
+      assert(it != sv.end());
+      assert(base((*it).begin()) == buffer + 4);
+      assert(base((*it).begin()) == buffer + 4);
+
+      ++it;
+      assert(it == sv.end());
+    }
+
+    // it++
+    {
+      auto it       = sv.begin();
+      auto original = it;
+
+      decltype(auto) it1 = it++; // trailing_empty is true
+      static_assert(std::is_same_v<decltype(it1), SplitIter>);
+      assert(it1 == original);
+
+      assert(it != sv.end());
+
+      assert(base((*it).begin()) == buffer + 4);
+      assert(base((*it).begin()) == buffer + 4);
+
+      it++;
+      assert(it == sv.end());
+    }
+  }
+}
+
+constexpr bool test() {
+  testOne<forward_iterator<int*>>();
+  testOne<bidirectional_iterator<int*>>();
+  testOne<random_access_iterator<int*>>();
+  testOne<contiguous_iterator<int*>>();
+  testOne<int*>();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/iterator/member_types.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/member_types.compile.pass.cpp
new file mode 100644
index 0000000000000..aa1a3467aeb7d
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/iterator/member_types.compile.pass.cpp
@@ -0,0 +1,53 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 split_view<V, P>::iterator.
+
+#include <concepts>
+#include <ranges>
+
+#include "test_macros.h"
+#include "test_iterators.h"
+
+template <class Iter, class PatternIter>
+constexpr void testIteratorTypedef() {
+  using Range     = std::ranges::subrange<Iter, sentinel_wrapper<Iter>>;
+  using Pattern   = std::ranges::subrange<PatternIter, sentinel_wrapper<PatternIter>>;
+  using SplitIter = std::ranges::iterator_t<std::ranges::split_view<Range, Pattern>>;
+
+  static_assert(std::same_as<typename SplitIter::iterator_concept, //
+                             std::forward_iterator_tag>);
+
+  static_assert(std::same_as<typename SplitIter::iterator_category, //
+                             std::input_iterator_tag>);
+
+  static_assert(std::same_as<typename SplitIter::value_type, //
+                             std::ranges::subrange<Iter>>);
+
+  static_assert(std::same_as<typename SplitIter::
diff erence_type, //
+                             std::iter_
diff erence_t<Iter>>);
+}
+
+template <class Iter>
+void testIteratorTypedefPattern() {
+  testIteratorTypedef<Iter, forward_iterator<int*>>();
+  testIteratorTypedef<Iter, bidirectional_iterator<int*>>();
+  testIteratorTypedef<Iter, random_access_iterator<int*>>();
+  testIteratorTypedef<Iter, contiguous_iterator<int*>>();
+  testIteratorTypedef<Iter, int*>();
+}
+
+void test() {
+  testIteratorTypedefPattern<forward_iterator<int*>>();
+  testIteratorTypedefPattern<bidirectional_iterator<int*>>();
+  testIteratorTypedefPattern<random_access_iterator<int*>>();
+  testIteratorTypedefPattern<contiguous_iterator<int*>>();
+  testIteratorTypedefPattern<int*>();
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/sentinel/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/sentinel/ctor.default.pass.cpp
new file mode 100644
index 0000000000000..7914af95deb97
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/sentinel/ctor.default.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
+
+// sentinel() = default;
+
+#include <cassert>
+#include <ranges>
+
+struct PODSentinel {
+  int i;
+
+  friend constexpr bool operator==(int*, const PODSentinel& p) { return p.i == 0; }
+};
+
+struct Range : std::ranges::view_base {
+  int* begin() const;
+  PODSentinel end();
+};
+
+constexpr bool test() {
+  using SplitView = std::ranges::split_view<Range, Range>;
+  using SplitIter = std::ranges::iterator_t<SplitView>;
+  using SplitSent = std::ranges::sentinel_t<SplitView>;
+  static_assert(!std::is_same_v<SplitSent, SplitIter>);
+
+  {
+    SplitIter it;
+    SplitSent s;
+    assert(s == it); // to make sure that s.__end_.i is initialised to 0;
+  }
+
+  {
+    SplitIter it;
+    SplitSent s = {};
+    assert(s == it); // to make sure that s.__end_.i is initialised to 0;
+  }
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/sentinel/ctor.parent.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/sentinel/ctor.parent.pass.cpp
new file mode 100644
index 0000000000000..c89b1ee2bdfce
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/sentinel/ctor.parent.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
+
+// constexpr explicit sentinel(split_view& parent);
+
+#include <cassert>
+#include <ranges>
+#include <type_traits>
+
+#include "test_iterators.h"
+
+// test explicit
+using Range     = std::ranges::subrange<int*, sentinel_wrapper<int*>>;
+using SplitView = std::ranges::split_view<Range, std::ranges::single_view<int>>;
+using SplitSent = std::ranges::sentinel_t<SplitView>;
+
+static_assert(std::is_constructible_v<SplitSent, SplitView&>);
+static_assert(!std::is_convertible_v<SplitView&, SplitSent>);
+
+constexpr bool test() {
+  {
+    int buffer[] = {0, 1, 2};
+    Range input{buffer, sentinel_wrapper<int*>(buffer + 3)};
+    SplitView sv(input, -1);
+    auto it = sv.begin();
+
+    SplitSent sent(sv);
+    assert(sent != it);
+
+    ++it;
+    assert(sent == it);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/sentinel/equal.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.split/sentinel/equal.pass.cpp
new file mode 100644
index 0000000000000..7b33a6b57fdd0
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/sentinel/equal.pass.cpp
@@ -0,0 +1,79 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// friend constexpr bool operator==(const iterator& x, const sentinel& y);
+
+#include <algorithm>
+#include <cassert>
+#include <concepts>
+#include <ranges>
+
+#include "test_iterators.h"
+
+template <class Iter>
+constexpr void testOne() {
+  using Sent      = sentinel_wrapper<Iter>;
+  using Range     = std::ranges::subrange<Iter, Sent>;
+  using SplitView = std::ranges::split_view<Range, std::ranges::single_view<int>>;
+  static_assert(!std::ranges::common_range<SplitView>);
+
+  {
+    // simple test
+    {
+      int buffer[] = {0, 1, 2, -1, 4, 5, 6};
+      Range input(Iter{buffer}, Sent{Iter{buffer + 7}});
+      SplitView sv(input, -1);
+      auto b = sv.begin();
+      auto e = sv.end();
+
+      assert(!(b == e));
+      assert(b != e);
+
+      std::advance(b, 2);
+      assert(b == e);
+      assert(!(b != e));
+    }
+
+    // iterator at trailing empty position should not equal to end
+    {
+      int buffer[] = {0, 1, 2, -1};
+      Range input(Iter{buffer}, Sent{Iter{buffer + 4}});
+      SplitView sv(input, -1);
+      auto b = sv.begin();
+      auto e = sv.end();
+
+      ++b; // cur points to end but trailing_empty is true
+
+      assert(b != e);
+      assert(!(b == e));
+
+      ++b;
+      assert(b == e);
+      assert(!(b != e));
+    }
+  }
+}
+
+constexpr bool test() {
+  testOne<forward_iterator<int*>>();
+  testOne<bidirectional_iterator<int*>>();
+  testOne<random_access_iterator<int*>>();
+  testOne<contiguous_iterator<int*>>();
+  testOne<int*>();
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.split/types.h b/libcxx/test/std/ranges/range.adaptors/range.split/types.h
new file mode 100644
index 0000000000000..ff2ce38317cd9
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/types.h
@@ -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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_SPLIT_TYPES_H
+#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_SPLIT_TYPES_H
+
+#include <functional>
+#include <ranges>
+
+#include "test_macros.h"
+#include "test_iterators.h"
+#include "test_range.h"
+
+template <class Derived>
+struct ForwardIterBase {
+  using iterator_concept = std::forward_iterator_tag;
+  using value_type       = int;
+  using 
diff erence_type  = intptr_t;
+
+  constexpr int operator*() const { return 5; }
+
+  constexpr Derived& operator++() { return static_cast<Derived&>(*this); }
+  constexpr Derived operator++(int) { return {}; }
+
+  friend constexpr bool operator==(const ForwardIterBase&, const ForwardIterBase&) { return true; };
+};
+
+#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_SPLIT_TYPES_H


        


More information about the libcxx-commits mailing list