[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