[libcxx-commits] [libcxx] [libc++] `std::views::split`: fix handling of empty ranges (LWG4017) (PR #87916)

Jan Kokemüller via libcxx-commits libcxx-commits at lists.llvm.org
Sun Apr 7 13:36:44 PDT 2024


https://github.com/jiixyj updated https://github.com/llvm/llvm-project/pull/87916

>From d11a134fbdf9d4b81597d2ffa8e5494b06934091 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= <jan.kokemueller at gmail.com>
Date: Sat, 6 Apr 2024 16:17:59 +0200
Subject: [PATCH 1/5] make 'views::split' return an empty range when given an
 empty range

---
 libcxx/include/__ranges/lazy_split_view.h     | 55 ++++++++++--------
 libcxx/include/__ranges/split_view.h          | 57 +++++--------------
 .../range.lazy.split/general.pass.cpp         | 18 +++++-
 .../range.lazy.split/view_interface.pass.cpp  | 14 -----
 .../range.split/general.pass.cpp              | 16 ++++++
 .../range.split/sentinel/ctor.parent.pass.cpp | 49 ----------------
 6 files changed, 79 insertions(+), 130 deletions(-)
 delete mode 100644 libcxx/test/std/ranges/range.adaptors/range.split/sentinel/ctor.parent.pass.cpp

diff --git a/libcxx/include/__ranges/lazy_split_view.h b/libcxx/include/__ranges/lazy_split_view.h
index 6aedfdabffe3a8..4f7a4873223497 100644
--- a/libcxx/include/__ranges/lazy_split_view.h
+++ b/libcxx/include/__ranges/lazy_split_view.h
@@ -103,7 +103,9 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
 
   _LIBCPP_HIDE_FROM_ABI constexpr auto begin() {
     if constexpr (forward_range<_View>) {
-      return __outer_iterator < __simple_view<_View> && __simple_view < _Pattern >> {*this, ranges::begin(__base_)};
+      // clang-format off
+      return __outer_iterator<__simple_view<_View> && __simple_view<_Pattern>>{*this, ranges::begin(__base_)};
+      // clang-format on
     } else {
       __current_.__emplace(ranges::begin(__base_));
       return __outer_iterator<false>{*this};
@@ -119,12 +121,14 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
   _LIBCPP_HIDE_FROM_ABI constexpr auto end()
     requires forward_range<_View> && common_range<_View>
   {
-    return __outer_iterator < __simple_view<_View> && __simple_view < _Pattern >> {*this, ranges::end(__base_)};
+    // clang-format off
+    return __outer_iterator<__simple_view<_View> && __simple_view<_Pattern>>{*this, {}};
+    // clang-format on
   }
 
   _LIBCPP_HIDE_FROM_ABI constexpr auto end() const {
     if constexpr (forward_range<_View> && forward_range<const _View> && common_range<const _View>) {
-      return __outer_iterator<true>{*this, ranges::end(__base_)};
+      return __outer_iterator<true>{*this, {}};
     } else {
       return default_sentinel;
     }
@@ -149,22 +153,23 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
     using _Parent = __maybe_const<_Const, lazy_split_view>;
     using _Base   = __maybe_const<_Const, _View>;
 
-    _Parent* __parent_                                 = nullptr;
-    using _MaybeCurrent                                = _If<forward_range<_View>, iterator_t<_Base>, __empty_cache>;
-    _LIBCPP_NO_UNIQUE_ADDRESS _MaybeCurrent __current_ = _MaybeCurrent();
-    bool __trailing_empty_                             = false;
+    _Parent* __parent_  = nullptr;
+    using _MaybeCurrent = _If<forward_range<_View>, iterator_t<_Base>, __empty_cache>;
+    _LIBCPP_NO_UNIQUE_ADDRESS optional<_MaybeCurrent> __current_ = nullopt;
 
+    // precondition: `__current_.has_value()`
     [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto& __current() noexcept {
       if constexpr (forward_range<_View>) {
-        return __current_;
+        return *__current_;
       } else {
         return *__parent_->__current_;
       }
     }
 
+    // precondition: `__current_.has_value()`
     [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const auto& __current() const noexcept {
       if constexpr (forward_range<_View>) {
-        return __current_;
+        return *__current_;
       } else {
         return *__parent_->__current_;
       }
@@ -195,9 +200,9 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
 
     _LIBCPP_HIDE_FROM_ABI constexpr explicit __outer_iterator(_Parent& __parent)
       requires(!forward_range<_Base>)
-        : __parent_(std::addressof(__parent)) {}
+        : __parent_(std::addressof(__parent)), __current_(in_place) {}
 
-    _LIBCPP_HIDE_FROM_ABI constexpr __outer_iterator(_Parent& __parent, iterator_t<_Base> __current)
+    _LIBCPP_HIDE_FROM_ABI constexpr __outer_iterator(_Parent& __parent, optional<iterator_t<_Base>> __current)
       requires forward_range<_Base>
         : __parent_(std::addressof(__parent)), __current_(std::move(__current)) {}
 
@@ -210,14 +215,16 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
     _LIBCPP_HIDE_FROM_ABI constexpr __outer_iterator& operator++() {
       const auto __end = ranges::end(__parent_->__base_);
       if (__current() == __end) {
-        __trailing_empty_ = false;
+        __current_.reset();
         return *this;
       }
 
       const auto [__pbegin, __pend] = ranges::subrange{__parent_->__pattern_};
       if (__pbegin == __pend) {
         // Empty pattern: split on every element in the input range
-        ++__current();
+        if (++__current() == __end) {
+          __current_.reset();
+        }
 
       } else if constexpr (__tiny_range<_Pattern>) {
         // One-element pattern: we can use `ranges::find`.
@@ -225,8 +232,8 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
         if (__current() != __end) {
           // Make sure we point to after the separator we just found.
           ++__current();
-          if (__current() == __end)
-            __trailing_empty_ = true;
+        } else {
+          __current_.reset();
         }
 
       } else {
@@ -235,12 +242,14 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
           const auto [__b, __p] = ranges::mismatch(__current(), __end, __pbegin, __pend);
           if (__p == __pend) {
             __current() = __b;
-            if (__current() == __end) {
-              __trailing_empty_ = true;
-            }
-            break; // The pattern matched; skip it.
+            // The pattern matched; skip it.
+            return *this;
           }
         } while (++__current() != __end);
+
+        // We arrived at the end of the range without matching the pattern,
+        // so we are done.
+        __current_.reset();
       }
 
       return *this;
@@ -260,12 +269,12 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
     _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __outer_iterator& __x, const __outer_iterator& __y)
       requires forward_range<_Base>
     {
-      return __x.__current_ == __y.__current_ && __x.__trailing_empty_ == __y.__trailing_empty_;
+      return __x.__current_ == __y.__current_;
     }
 
     _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __outer_iterator& __x, default_sentinel_t) {
       _LIBCPP_ASSERT_NON_NULL(__x.__parent_ != nullptr, "Cannot call comparison on a default-constructed iterator.");
-      return __x.__current() == ranges::end(__x.__parent_base()) && !__x.__trailing_empty_;
+      return !__x.__current_.has_value();
     }
   };
 
@@ -403,8 +412,8 @@ template <class _Range, class _Pattern>
 lazy_split_view(_Range&&, _Pattern&&) -> lazy_split_view<views::all_t<_Range>, views::all_t<_Pattern>>;
 
 template <input_range _Range>
-lazy_split_view(_Range&&, range_value_t<_Range>)
-    -> lazy_split_view<views::all_t<_Range>, single_view<range_value_t<_Range>>>;
+lazy_split_view(_Range&&,
+                range_value_t<_Range>) -> lazy_split_view<views::all_t<_Range>, single_view<range_value_t<_Range>>>;
 
 namespace views {
 namespace __lazy_split_view {
diff --git a/libcxx/include/__ranges/split_view.h b/libcxx/include/__ranges/split_view.h
index 98f17be04f628f..ba8fbf1343a48f 100644
--- a/libcxx/include/__ranges/split_view.h
+++ b/libcxx/include/__ranges/split_view.h
@@ -15,6 +15,7 @@
 #include <__config>
 #include <__functional/bind_back.h>
 #include <__functional/ranges_operations.h>
+#include <__iterator/default_sentinel.h>
 #include <__iterator/indirectly_comparable.h>
 #include <__iterator/iterator_traits.h>
 #include <__memory/addressof.h>
@@ -58,11 +59,7 @@ class split_view : public view_interface<split_view<_View, _Pattern>> {
   template <class, class>
   friend struct __iterator;
 
-  template <class, class>
-  friend struct __sentinel;
-
   struct __iterator;
-  struct __sentinel;
 
   _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_);
@@ -107,7 +104,7 @@ class split_view : public view_interface<split_view<_View, _Pattern>> {
     if constexpr (common_range<_View>) {
       return __iterator{*this, ranges::end(__base_), {}};
     } else {
-      return __sentinel{*this};
+      return default_sentinel;
     }
   }
 };
@@ -123,12 +120,9 @@ template <forward_range _View, forward_range _Pattern>
            indirectly_comparable<iterator_t<_View>, iterator_t<_Pattern>, ranges::equal_to>
 struct split_view<_View, _Pattern>::__iterator {
 private:
-  split_view* __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;
-
-  friend struct __sentinel;
+  split_view* __parent_                                                   = nullptr;
+  _LIBCPP_NO_UNIQUE_ADDRESS iterator_t<_View> __cur_                      = iterator_t<_View>();
+  _LIBCPP_NO_UNIQUE_ADDRESS optional<subrange<iterator_t<_View>>> __next_ = nullopt;
 
 public:
   using iterator_concept  = forward_iterator_tag;
@@ -139,25 +133,20 @@ struct split_view<_View, _Pattern>::__iterator {
   _LIBCPP_HIDE_FROM_ABI __iterator() = default;
 
   _LIBCPP_HIDE_FROM_ABI constexpr __iterator(
-      split_view<_View, _Pattern>& __parent, iterator_t<_View> __current, subrange<iterator_t<_View>> __next)
+      split_view<_View, _Pattern>& __parent, iterator_t<_View> __current, optional<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 value_type operator*() const { return {__cur_, __next_->begin()}; }
 
   _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() {
-    __cur_ = __next_.begin();
+    __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_);
-      }
+      __cur_  = __next_->end();
+      __next_ = __parent_->__find_next(__cur_);
     } else {
-      __trailing_empty_ = false;
+      __next_.reset();
     }
     return *this;
   }
@@ -169,29 +158,11 @@ struct split_view<_View, _Pattern>::__iterator {
   }
 
   _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __iterator& __y) {
-    return __x.__cur_ == __y.__cur_ && __x.__trailing_empty_ == __y.__trailing_empty_;
-  }
-};
-
-template <forward_range _View, forward_range _Pattern>
-  requires view<_View> && view<_Pattern> &&
-           indirectly_comparable<iterator_t<_View>, iterator_t<_Pattern>, ranges::equal_to>
-struct split_view<_View, _Pattern>::__sentinel {
-private:
-  _LIBCPP_NO_UNIQUE_ADDRESS sentinel_t<_View> __end_ = sentinel_t<_View>();
-
-  _LIBCPP_HIDE_FROM_ABI static constexpr bool __equals(const __iterator& __x, const __sentinel& __y) {
-    return __x.__cur_ == __y.__end_ && !__x.__trailing_empty_;
+    return __x.__cur_ == __y.__cur_ && __x.__next_.has_value() == __y.__next_.has_value();
   }
 
-public:
-  _LIBCPP_HIDE_FROM_ABI __sentinel() = default;
-
-  _LIBCPP_HIDE_FROM_ABI constexpr explicit __sentinel(split_view<_View, _Pattern>& __parent)
-      : __end_(ranges::end(__parent.__base_)) {}
-
-  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __sentinel& __y) {
-    return __equals(__x, __y);
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, default_sentinel_t) {
+    return !__x.__next_.has_value();
   }
 };
 
diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/general.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/general.pass.cpp
index f4e87bb47399ef..e90232f818f4d9 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/general.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/general.pass.cpp
@@ -213,6 +213,22 @@ constexpr bool test_string_literals() {
     assert(test_with_piping("", long_sep, expected));
   }
 
+  // Splitting an empty `string_view` should result in one (empty) range.
+  // See LWG issue 4017.
+  {
+    std::array expected = {std::string_view()};
+
+    assert(test_function_call(std::string_view(), short_sep, expected));
+    assert(test_with_piping(std::string_view(), short_sep, expected));
+    assert(test_function_call(std::string_view(), long_sep, expected));
+    assert(test_with_piping(std::string_view(), long_sep, expected));
+
+    assert(test_function_call(std::string_view(""), short_sep, expected));
+    assert(test_with_piping(std::string_view(""), short_sep, expected));
+    assert(test_function_call(std::string_view(""), long_sep, expected));
+    assert(test_with_piping(std::string_view(""), long_sep, expected));
+  }
+
   // Terminating null in the separator -- the character literal `' '` and the seemingly equivalent string literal `" "`
   // are treated differently due to the presence of an implicit `\0` in the latter.
   {
@@ -367,7 +383,7 @@ constexpr bool main_test() {
 
   // Empty input.
   {
-    std::array<std::string_view, 0> expected = {};
+    std::array expected = {""sv};
     test_one(""sv, short_sep, expected);
     test_one(""sv, long_sep, expected);
   }
diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/view_interface.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/view_interface.pass.cpp
index 8d5982d59759dd..350fc82d854eaf 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/view_interface.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/view_interface.pass.cpp
@@ -31,13 +31,6 @@ constexpr bool test() {
       std::ranges::lazy_split_view v("abc def", " ");
       assert(!v.empty());
     }
-
-    {
-      // Note: an empty string literal would still produce a non-empty output because the terminating zero is treated as
-      // a separate character; hence the use of `string_view`.
-      std::ranges::lazy_split_view v(""sv, "");
-      assert(v.empty());
-    }
   }
 
   // operator bool()
@@ -46,13 +39,6 @@ constexpr bool test() {
       std::ranges::lazy_split_view v("abc", "");
       assert(v);
     }
-
-    {
-      // Note: an empty string literal would still produce a non-empty output because the terminating zero is treated as
-      // a separate character; hence the use of `string_view`.
-      std::ranges::lazy_split_view v(""sv, "");
-      assert(!v);
-    }
   }
 
   // front()
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
index 5389d931f840e7..8b1303cfddf378 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.split/general.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.split/general.pass.cpp
@@ -161,6 +161,22 @@ constexpr bool test_string_literals() {
     assert(test_with_piping("", long_sep, expected));
   }
 
+  // Splitting an empty `string_view` should result in one (empty) range.
+  // See LWG issue 4017.
+  {
+    std::array expected = {std::string_view()};
+
+    assert(test_function_call(std::string_view(), short_sep, expected));
+    assert(test_with_piping(std::string_view(), short_sep, expected));
+    assert(test_function_call(std::string_view(), long_sep, expected));
+    assert(test_with_piping(std::string_view(), long_sep, expected));
+
+    assert(test_function_call(std::string_view(""), short_sep, expected));
+    assert(test_with_piping(std::string_view(""), short_sep, expected));
+    assert(test_function_call(std::string_view(""), long_sep, expected));
+    assert(test_with_piping(std::string_view(""), long_sep, expected));
+  }
+
   // Terminating null in the separator -- the character literal `' '` and the seemingly equivalent string literal `" "`
   // are treated differently due to the presence of an implicit `\0` in the latter.
   {
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
deleted file mode 100644
index c89b1ee2bdfce5..00000000000000
--- a/libcxx/test/std/ranges/range.adaptors/range.split/sentinel/ctor.parent.pass.cpp
+++ /dev/null
@@ -1,49 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// 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;
-}

>From f310d57ee0764da4cb0ddcf4c398f8ce92c108e0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= <jan.kokemueller at gmail.com>
Date: Sun, 7 Apr 2024 13:35:27 +0200
Subject: [PATCH 2/5] include '<optional>' in split_view.h and
 lazy_split_view.h

---
 libcxx/include/__ranges/lazy_split_view.h | 1 +
 libcxx/include/__ranges/split_view.h      | 1 +
 2 files changed, 2 insertions(+)

diff --git a/libcxx/include/__ranges/lazy_split_view.h b/libcxx/include/__ranges/lazy_split_view.h
index 4f7a4873223497..ef944c25f7aac7 100644
--- a/libcxx/include/__ranges/lazy_split_view.h
+++ b/libcxx/include/__ranges/lazy_split_view.h
@@ -42,6 +42,7 @@
 #include <__type_traits/remove_reference.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
+#include <optional>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
diff --git a/libcxx/include/__ranges/split_view.h b/libcxx/include/__ranges/split_view.h
index ba8fbf1343a48f..93351231439435 100644
--- a/libcxx/include/__ranges/split_view.h
+++ b/libcxx/include/__ranges/split_view.h
@@ -32,6 +32,7 @@
 #include <__type_traits/is_nothrow_constructible.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
+#include <optional>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header

>From a18c790eb3879a84ce7b4b321687c5b2d1c03ceb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= <jan.kokemueller at gmail.com>
Date: Sun, 7 Apr 2024 16:38:19 +0200
Subject: [PATCH 3/5] try to fix test failures

---
 .../range.lazy.split.outer/ctor.parent_base.pass.cpp     | 6 ++++--
 .../range.adaptors/range.split/iterator/base.pass.cpp    | 9 +++++----
 .../range.split/iterator/ctor.base.pass.cpp              | 8 ++++++--
 .../range.adaptors/range.split/iterator/deref.pass.cpp   | 5 +++--
 4 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent_base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent_base.pass.cpp
index 9fc0ce3748b1ff..807dbb07cb2cde 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent_base.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent_base.pass.cpp
@@ -8,11 +8,12 @@
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
-// constexpr outer-iterator(Parent& parent, iterator_t<Base> current);
+// constexpr outer-iterator(Parent& parent, optional<iterator_t<Base>> current);
 //   requires forward_range<Base>
 
 #include <ranges>
 
+#include <optional>
 #include <type_traits>
 #include <utility>
 #include "../types.h"
@@ -23,7 +24,8 @@ static_assert(!std::is_constructible_v<OuterIterInput, SplitViewInput&, std::ran
 constexpr bool test() {
   ForwardView input("abc");
   SplitViewForward v(std::move(input), " ");
-  [[maybe_unused]] OuterIterForward i(v, input.begin());
+  [[maybe_unused]] OuterIterForward i1(v, std::make_optional(input.begin()));
+  [[maybe_unused]] OuterIterForward i2(v, std::nullopt);
 
   return true;
 }
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
index 325189a0e521e9..5278129bd8cada 100644
--- 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
@@ -11,6 +11,7 @@
 // constexpr iterator_t<V> base() const;
 
 #include <cassert>
+#include <optional>
 #include <ranges>
 
 #include "../types.h"
@@ -29,7 +30,7 @@ constexpr bool test() {
   // const &
   {
     SplitView sv;
-    const SplitIter it{sv, Iter{5}, {}};
+    const SplitIter it{sv, Iter{5}, std::nullopt};
     std::same_as<Iter> decltype(auto) base = it.base();
     assert(base.i == 5);
   }
@@ -37,7 +38,7 @@ constexpr bool test() {
   // &
   {
     SplitView sv;
-    SplitIter it{sv, Iter{5}, {}};
+    SplitIter it{sv, Iter{5}, std::nullopt};
     std::same_as<Iter> decltype(auto) base = it.base();
     assert(base.i == 5);
   }
@@ -45,7 +46,7 @@ constexpr bool test() {
   // &&
   {
     SplitView sv;
-    SplitIter it{sv, Iter{5}, {}};
+    SplitIter it{sv, Iter{5}, std::nullopt};
     std::same_as<Iter> decltype(auto) base = std::move(it).base();
     assert(base.i == 5);
   }
@@ -53,7 +54,7 @@ constexpr bool test() {
   // const &&
   {
     SplitView sv;
-    const SplitIter it{sv, Iter{5}, {}};
+    const SplitIter it{sv, Iter{5}, std::nullopt};
     std::same_as<Iter> decltype(auto) base = std::move(it).base();
     assert(base.i == 5);
   }
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
index 20b3c19611bd0d..19d534706c36db 100644
--- 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
@@ -8,9 +8,10 @@
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
-// constexpr iterator(split_view& parent, iterator_t<V> current, subrange<iterator_t<V>> next);
+// constexpr iterator(split_view& parent, iterator_t<V> current, optional<subrange<iterator_t<V>>> next);
 
 #include <cassert>
+#include <optional>
 #include <ranges>
 
 #include "../types.h"
@@ -35,13 +36,16 @@ constexpr bool test() {
   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()}};
+  SplitIter iter = {sv, sv.base().begin(), std::make_optional(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);
 
+  SplitIter iter2 = {sv, sv.base().begin(), std::nullopt};
+  assert(iter2.base().moved);
+
   return true;
 }
 
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
index 721a1cc0da3d49..ffa1af8b04967a 100644
--- 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
@@ -9,9 +9,10 @@
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
 // constexpr value_type operator*() const;
-//   Effects: Equivalent to return {cur_, next_.begin()};
+//   Effects: Equivalent to return {cur_, next_->begin()};
 
 #include <cassert>
+#include <optional>
 #include <ranges>
 
 #include "../types.h"
@@ -30,7 +31,7 @@ constexpr bool test() {
     SplitView sv;
     Iter current{5};
     std::ranges::subrange next{Iter{6}, Iter{7}};
-    const SplitIter it{sv, current, next};
+    const SplitIter it{sv, current, std::make_optional(next)};
     std::same_as<std::ranges::subrange<Iter>> decltype(auto) value = *it;
     assert(value.begin().i == 5);
     assert(value.end().i == 6);

>From 1f631204d5d90bfa60b83e0267520d2dcfdcb042 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= <jan.kokemueller at gmail.com>
Date: Sun, 7 Apr 2024 16:41:46 +0200
Subject: [PATCH 4/5] autoformat

---
 .../range.adaptors/range.split/iterator/ctor.base.pass.cpp    | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

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
index 19d534706c36db..ec84f3a510b19d 100644
--- 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
@@ -36,7 +36,9 @@ constexpr bool test() {
   using SplitIter = std::ranges::iterator_t<SplitView>;
 
   SplitView sv{TracedMoveView{}, TracedMoveView{}};
-  SplitIter iter = {sv, sv.base().begin(), std::make_optional(std::ranges::subrange<TracedMoveIter>{sv.base().begin(), sv.base().end()})};
+  SplitIter iter = {sv,
+                    sv.base().begin(),
+                    std::make_optional(std::ranges::subrange<TracedMoveIter>{sv.base().begin(), sv.base().end()})};
   assert(iter.base().moved);
 
   auto subRange = *iter;

>From e16f7aa944669280534fba3155dbb5cad6a3988f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jan=20Kokem=C3=BCller?= <jan.kokemueller at gmail.com>
Date: Sun, 7 Apr 2024 22:36:20 +0200
Subject: [PATCH 5/5] implement alternative design

---
 libcxx/include/__ranges/lazy_split_view.h     | 53 ++++++++------
 libcxx/include/__ranges/split_view.h          | 69 ++++++++++++++-----
 .../ctor.parent_base.pass.cpp                 |  6 +-
 .../range.split/iterator/base.pass.cpp        |  9 ++-
 .../range.split/iterator/ctor.base.pass.cpp   | 10 +--
 .../range.split/iterator/deref.pass.cpp       |  5 +-
 .../range.split/sentinel/ctor.parent.pass.cpp | 49 +++++++++++++
 7 files changed, 145 insertions(+), 56 deletions(-)
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.split/sentinel/ctor.parent.pass.cpp

diff --git a/libcxx/include/__ranges/lazy_split_view.h b/libcxx/include/__ranges/lazy_split_view.h
index ef944c25f7aac7..0ff1db54651f1d 100644
--- a/libcxx/include/__ranges/lazy_split_view.h
+++ b/libcxx/include/__ranges/lazy_split_view.h
@@ -42,7 +42,6 @@
 #include <__type_traits/remove_reference.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
-#include <optional>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -123,13 +122,14 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
     requires forward_range<_View> && common_range<_View>
   {
     // clang-format off
-    return __outer_iterator<__simple_view<_View> && __simple_view<_Pattern>>{*this, {}};
+    return __outer_iterator<__simple_view<_View> &&
+                            __simple_view<_Pattern>>{*this, ranges::end(__base_), __outer_iterator_at_end{}};
     // clang-format on
   }
 
   _LIBCPP_HIDE_FROM_ABI constexpr auto end() const {
     if constexpr (forward_range<_View> && forward_range<const _View> && common_range<const _View>) {
-      return __outer_iterator<true>{*this, {}};
+      return __outer_iterator<true>{*this, ranges::end(__base_), __outer_iterator_at_end{}};
     } else {
       return default_sentinel;
     }
@@ -144,6 +144,10 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
     using iterator_category = input_iterator_tag;
   };
 
+  struct __outer_iterator_at_end {
+    constexpr explicit __outer_iterator_at_end() = default;
+  };
+
   template <bool _Const>
   struct __outer_iterator : __outer_iterator_category<__maybe_const<_Const, _View>> {
   private:
@@ -154,23 +158,24 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
     using _Parent = __maybe_const<_Const, lazy_split_view>;
     using _Base   = __maybe_const<_Const, _View>;
 
-    _Parent* __parent_  = nullptr;
-    using _MaybeCurrent = _If<forward_range<_View>, iterator_t<_Base>, __empty_cache>;
-    _LIBCPP_NO_UNIQUE_ADDRESS optional<_MaybeCurrent> __current_ = nullopt;
+    _Parent* __parent_                                 = nullptr;
+    using _MaybeCurrent                                = _If<forward_range<_View>, iterator_t<_Base>, __empty_cache>;
+    _LIBCPP_NO_UNIQUE_ADDRESS _MaybeCurrent __current_ = _MaybeCurrent();
+    bool __has_more_                                   = false;
 
-    // precondition: `__current_.has_value()`
+    // precondition: `__has_more_`
     [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr auto& __current() noexcept {
       if constexpr (forward_range<_View>) {
-        return *__current_;
+        return __current_;
       } else {
         return *__parent_->__current_;
       }
     }
 
-    // precondition: `__current_.has_value()`
+    // precondition: `__has_more_`
     [[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr const auto& __current() const noexcept {
       if constexpr (forward_range<_View>) {
-        return *__current_;
+        return __current_;
       } else {
         return *__parent_->__current_;
       }
@@ -201,22 +206,30 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
 
     _LIBCPP_HIDE_FROM_ABI constexpr explicit __outer_iterator(_Parent& __parent)
       requires(!forward_range<_Base>)
-        : __parent_(std::addressof(__parent)), __current_(in_place) {}
+        : __parent_(std::addressof(__parent)), __has_more_(true) {}
+
+    _LIBCPP_HIDE_FROM_ABI constexpr __outer_iterator(_Parent& __parent, iterator_t<_Base> __current)
+      requires forward_range<_Base>
+        : __parent_(std::addressof(__parent)), __current_(std::move(__current)), __has_more_(true) {}
 
-    _LIBCPP_HIDE_FROM_ABI constexpr __outer_iterator(_Parent& __parent, optional<iterator_t<_Base>> __current)
+    // libc++ extension: constructs an `__outer_iterator` that is at the end of
+    // the `lazy_split_view`. Needs to be called with the end iterator of the
+    // underlying range.
+    _LIBCPP_HIDE_FROM_ABI constexpr __outer_iterator(
+        _Parent& __parent, iterator_t<_Base> __current, __outer_iterator_at_end)
       requires forward_range<_Base>
-        : __parent_(std::addressof(__parent)), __current_(std::move(__current)) {}
+        : __parent_(std::addressof(__parent)), __current_(std::move(__current)), __has_more_(false) {}
 
     _LIBCPP_HIDE_FROM_ABI constexpr __outer_iterator(__outer_iterator<!_Const> __i)
       requires _Const && convertible_to<iterator_t<_View>, iterator_t<_Base>>
-        : __parent_(__i.__parent_), __current_(std::move(__i.__current_)) {}
+        : __parent_(__i.__parent_), __current_(std::move(__i.__current_)), __has_more_(__i.__has_more_) {}
 
     _LIBCPP_HIDE_FROM_ABI constexpr value_type operator*() const { return value_type{*this}; }
 
     _LIBCPP_HIDE_FROM_ABI constexpr __outer_iterator& operator++() {
       const auto __end = ranges::end(__parent_->__base_);
       if (__current() == __end) {
-        __current_.reset();
+        __has_more_ = false;
         return *this;
       }
 
@@ -224,7 +237,7 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
       if (__pbegin == __pend) {
         // Empty pattern: split on every element in the input range
         if (++__current() == __end) {
-          __current_.reset();
+          __has_more_ = false;
         }
 
       } else if constexpr (__tiny_range<_Pattern>) {
@@ -234,7 +247,7 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
           // Make sure we point to after the separator we just found.
           ++__current();
         } else {
-          __current_.reset();
+          __has_more_ = false;
         }
 
       } else {
@@ -250,7 +263,7 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
 
         // We arrived at the end of the range without matching the pattern,
         // so we are done.
-        __current_.reset();
+        __has_more_ = false;
       }
 
       return *this;
@@ -270,12 +283,12 @@ class lazy_split_view : public view_interface<lazy_split_view<_View, _Pattern>>
     _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __outer_iterator& __x, const __outer_iterator& __y)
       requires forward_range<_Base>
     {
-      return __x.__current_ == __y.__current_;
+      return __x.__current_ == __y.__current_ && __x.__has_more_ == __y.__has_more_;
     }
 
     _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __outer_iterator& __x, default_sentinel_t) {
       _LIBCPP_ASSERT_NON_NULL(__x.__parent_ != nullptr, "Cannot call comparison on a default-constructed iterator.");
-      return !__x.__current_.has_value();
+      return !__x.__has_more_;
     }
   };
 
diff --git a/libcxx/include/__ranges/split_view.h b/libcxx/include/__ranges/split_view.h
index 93351231439435..2f25857a17ff01 100644
--- a/libcxx/include/__ranges/split_view.h
+++ b/libcxx/include/__ranges/split_view.h
@@ -15,7 +15,6 @@
 #include <__config>
 #include <__functional/bind_back.h>
 #include <__functional/ranges_operations.h>
-#include <__iterator/default_sentinel.h>
 #include <__iterator/indirectly_comparable.h>
 #include <__iterator/iterator_traits.h>
 #include <__memory/addressof.h>
@@ -32,7 +31,6 @@
 #include <__type_traits/is_nothrow_constructible.h>
 #include <__utility/forward.h>
 #include <__utility/move.h>
-#include <optional>
 
 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
 #  pragma GCC system_header
@@ -57,10 +55,18 @@ class split_view : public view_interface<split_view<_View, _Pattern>> {
   using _Cache                                  = __non_propagating_cache<subrange<iterator_t<_View>>>;
   _Cache __cached_begin_                        = _Cache();
 
+  struct __iterator_at_end {
+    constexpr explicit __iterator_at_end() = default;
+  };
+
   template <class, class>
   friend struct __iterator;
 
+  template <class, class>
+  friend struct __sentinel;
+
   struct __iterator;
+  struct __sentinel;
 
   _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_);
@@ -103,9 +109,9 @@ class split_view : public view_interface<split_view<_View, _Pattern>> {
 
   _LIBCPP_HIDE_FROM_ABI constexpr auto end() {
     if constexpr (common_range<_View>) {
-      return __iterator{*this, ranges::end(__base_), {}};
+      return __iterator{*this, ranges::end(__base_), __iterator_at_end{}};
     } else {
-      return default_sentinel;
+      return __sentinel{*this};
     }
   }
 };
@@ -121,9 +127,12 @@ template <forward_range _View, forward_range _Pattern>
            indirectly_comparable<iterator_t<_View>, iterator_t<_Pattern>, ranges::equal_to>
 struct split_view<_View, _Pattern>::__iterator {
 private:
-  split_view* __parent_                                                   = nullptr;
-  _LIBCPP_NO_UNIQUE_ADDRESS iterator_t<_View> __cur_                      = iterator_t<_View>();
-  _LIBCPP_NO_UNIQUE_ADDRESS optional<subrange<iterator_t<_View>>> __next_ = nullopt;
+  split_view* __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 __has_more_                                              = false;
+
+  friend struct __sentinel;
 
 public:
   using iterator_concept  = forward_iterator_tag;
@@ -134,20 +143,30 @@ struct split_view<_View, _Pattern>::__iterator {
   _LIBCPP_HIDE_FROM_ABI __iterator() = default;
 
   _LIBCPP_HIDE_FROM_ABI constexpr __iterator(
-      split_view<_View, _Pattern>& __parent, iterator_t<_View> __current, optional<subrange<iterator_t<_View>>> __next)
-      : __parent_(std::addressof(__parent)), __cur_(std::move(__current)), __next_(std::move(__next)) {}
+      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)),
+        __has_more_(true) {}
+
+  // libc++ extension: constructs an `__iterator` that is at the end of
+  // the `split_view`. Needs to be called with the end iterator of the
+  // underlying range.
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator(
+      split_view<_View, _Pattern>& __parent, iterator_t<_View> __current, __iterator_at_end)
+      : __parent_(std::addressof(__parent)), __cur_(std::move(__current)), __has_more_(false) {}
 
   _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 value_type operator*() const { return {__cur_, __next_.begin()}; }
 
   _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() {
-    __cur_ = __next_->begin();
+    __cur_ = __next_.begin();
     if (__cur_ != ranges::end(__parent_->__base_)) {
-      __cur_  = __next_->end();
+      __cur_  = __next_.end();
       __next_ = __parent_->__find_next(__cur_);
     } else {
-      __next_.reset();
+      __has_more_ = false;
     }
     return *this;
   }
@@ -159,11 +178,29 @@ struct split_view<_View, _Pattern>::__iterator {
   }
 
   _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __iterator& __y) {
-    return __x.__cur_ == __y.__cur_ && __x.__next_.has_value() == __y.__next_.has_value();
+    return __x.__cur_ == __y.__cur_ && __x.__has_more_ == __y.__has_more_;
+  }
+};
+
+template <forward_range _View, forward_range _Pattern>
+  requires view<_View> && view<_Pattern> &&
+           indirectly_comparable<iterator_t<_View>, iterator_t<_Pattern>, ranges::equal_to>
+struct split_view<_View, _Pattern>::__sentinel {
+private:
+  _LIBCPP_NO_UNIQUE_ADDRESS sentinel_t<_View> __end_ = sentinel_t<_View>();
+
+  _LIBCPP_HIDE_FROM_ABI static constexpr bool __equals(const __iterator& __x, const __sentinel& __y) {
+    return __x.__cur_ == __y.__end_ && !__x.__has_more_;
   }
 
-  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, default_sentinel_t) {
-    return !__x.__next_.has_value();
+public:
+  _LIBCPP_HIDE_FROM_ABI __sentinel() = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit __sentinel(split_view<_View, _Pattern>& __parent)
+      : __end_(ranges::end(__parent.__base_)) {}
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __sentinel& __y) {
+    return __equals(__x, __y);
   }
 };
 
diff --git a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent_base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent_base.pass.cpp
index 807dbb07cb2cde..9fc0ce3748b1ff 100644
--- a/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent_base.pass.cpp
+++ b/libcxx/test/std/ranges/range.adaptors/range.lazy.split/range.lazy.split.outer/ctor.parent_base.pass.cpp
@@ -8,12 +8,11 @@
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
-// constexpr outer-iterator(Parent& parent, optional<iterator_t<Base>> current);
+// constexpr outer-iterator(Parent& parent, iterator_t<Base> current);
 //   requires forward_range<Base>
 
 #include <ranges>
 
-#include <optional>
 #include <type_traits>
 #include <utility>
 #include "../types.h"
@@ -24,8 +23,7 @@ static_assert(!std::is_constructible_v<OuterIterInput, SplitViewInput&, std::ran
 constexpr bool test() {
   ForwardView input("abc");
   SplitViewForward v(std::move(input), " ");
-  [[maybe_unused]] OuterIterForward i1(v, std::make_optional(input.begin()));
-  [[maybe_unused]] OuterIterForward i2(v, std::nullopt);
+  [[maybe_unused]] OuterIterForward i(v, input.begin());
 
   return true;
 }
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
index 5278129bd8cada..325189a0e521e9 100644
--- 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
@@ -11,7 +11,6 @@
 // constexpr iterator_t<V> base() const;
 
 #include <cassert>
-#include <optional>
 #include <ranges>
 
 #include "../types.h"
@@ -30,7 +29,7 @@ constexpr bool test() {
   // const &
   {
     SplitView sv;
-    const SplitIter it{sv, Iter{5}, std::nullopt};
+    const SplitIter it{sv, Iter{5}, {}};
     std::same_as<Iter> decltype(auto) base = it.base();
     assert(base.i == 5);
   }
@@ -38,7 +37,7 @@ constexpr bool test() {
   // &
   {
     SplitView sv;
-    SplitIter it{sv, Iter{5}, std::nullopt};
+    SplitIter it{sv, Iter{5}, {}};
     std::same_as<Iter> decltype(auto) base = it.base();
     assert(base.i == 5);
   }
@@ -46,7 +45,7 @@ constexpr bool test() {
   // &&
   {
     SplitView sv;
-    SplitIter it{sv, Iter{5}, std::nullopt};
+    SplitIter it{sv, Iter{5}, {}};
     std::same_as<Iter> decltype(auto) base = std::move(it).base();
     assert(base.i == 5);
   }
@@ -54,7 +53,7 @@ constexpr bool test() {
   // const &&
   {
     SplitView sv;
-    const SplitIter it{sv, Iter{5}, std::nullopt};
+    const SplitIter it{sv, Iter{5}, {}};
     std::same_as<Iter> decltype(auto) base = std::move(it).base();
     assert(base.i == 5);
   }
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
index ec84f3a510b19d..20b3c19611bd0d 100644
--- 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
@@ -8,10 +8,9 @@
 
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
-// constexpr iterator(split_view& parent, iterator_t<V> current, optional<subrange<iterator_t<V>>> next);
+// constexpr iterator(split_view& parent, iterator_t<V> current, subrange<iterator_t<V>> next);
 
 #include <cassert>
-#include <optional>
 #include <ranges>
 
 #include "../types.h"
@@ -36,18 +35,13 @@ constexpr bool test() {
   using SplitIter = std::ranges::iterator_t<SplitView>;
 
   SplitView sv{TracedMoveView{}, TracedMoveView{}};
-  SplitIter iter = {sv,
-                    sv.base().begin(),
-                    std::make_optional(std::ranges::subrange<TracedMoveIter>{sv.base().begin(), sv.base().end()})};
+  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);
 
-  SplitIter iter2 = {sv, sv.base().begin(), std::nullopt};
-  assert(iter2.base().moved);
-
   return true;
 }
 
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
index ffa1af8b04967a..721a1cc0da3d49 100644
--- 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
@@ -9,10 +9,9 @@
 // UNSUPPORTED: c++03, c++11, c++14, c++17
 
 // constexpr value_type operator*() const;
-//   Effects: Equivalent to return {cur_, next_->begin()};
+//   Effects: Equivalent to return {cur_, next_.begin()};
 
 #include <cassert>
-#include <optional>
 #include <ranges>
 
 #include "../types.h"
@@ -31,7 +30,7 @@ constexpr bool test() {
     SplitView sv;
     Iter current{5};
     std::ranges::subrange next{Iter{6}, Iter{7}};
-    const SplitIter it{sv, current, std::make_optional(next)};
+    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);
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 00000000000000..c89b1ee2bdfce5
--- /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;
+}



More information about the libcxx-commits mailing list