[libcxx-commits] [libcxx] 065dc48 - [libc++][ranges] Implement P2443R1: `views::chunk_by`

Konstantin Varlamov via libcxx-commits libcxx-commits at lists.llvm.org
Tue Sep 5 16:20:01 PDT 2023


Author: Jakub Mazurkiewicz
Date: 2023-09-05T16:19:49-07:00
New Revision: 065dc485bd4b33f6110993cc4b353f1e7c36cac3

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

LOG: [libc++][ranges] Implement P2443R1: `views::chunk_by`

This patch implements https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2443r1.html (`views::chunk_by`).

Reviewed By: #libc, var-const

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

Added: 
    libcxx/include/__ranges/chunk_by_view.h
    libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/adaptor.nodiscard.verify.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.begin.pass.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.find-next.pass.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.find-prev.pass.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/assert.deref.pass.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/assert.increment.pass.cpp
    libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/types.h
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/adaptor.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/base.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/begin.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/constraints.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctad.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctor.default.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctor.view_pred.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/end.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/pred.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/compare.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/ctor.default.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/decrement.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/deref.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/increment.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/types.compile.pass.cpp
    libcxx/test/std/ranges/range.adaptors/range.chunk.by/types.h

Modified: 
    libcxx/docs/FeatureTestMacroTable.rst
    libcxx/docs/ReleaseNotes/18.rst
    libcxx/docs/Status/Cxx23Papers.csv
    libcxx/docs/Status/RangesViews.csv
    libcxx/include/CMakeLists.txt
    libcxx/include/__algorithm/ranges_adjacent_find.h
    libcxx/include/__ranges/movable_box.h
    libcxx/include/module.modulemap.in
    libcxx/include/ranges
    libcxx/include/version
    libcxx/modules/std/ranges.inc
    libcxx/test/libcxx/transitive_includes/cxx03.csv
    libcxx/test/libcxx/transitive_includes/cxx11.csv
    libcxx/test/libcxx/transitive_includes/cxx14.csv
    libcxx/test/libcxx/transitive_includes/cxx17.csv
    libcxx/test/libcxx/transitive_includes/cxx20.csv
    libcxx/test/libcxx/transitive_includes/cxx23.csv
    libcxx/test/libcxx/transitive_includes/cxx26.csv
    libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
    libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
    libcxx/utils/generate_feature_test_macro_components.py

Removed: 
    


################################################################################
diff  --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index 63abcbb886ab222..e1b4172b22c53da 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -346,7 +346,7 @@ Status
     --------------------------------------------------- -----------------
     ``__cpp_lib_ranges_chunk``                          *unimplemented*
     --------------------------------------------------- -----------------
-    ``__cpp_lib_ranges_chunk_by``                       *unimplemented*
+    ``__cpp_lib_ranges_chunk_by``                       ``202202L``
     --------------------------------------------------- -----------------
     ``__cpp_lib_ranges_iota``                           *unimplemented*
     --------------------------------------------------- -----------------

diff  --git a/libcxx/docs/ReleaseNotes/18.rst b/libcxx/docs/ReleaseNotes/18.rst
index 1d08693adb02e8b..b10c8fa78c2270d 100644
--- a/libcxx/docs/ReleaseNotes/18.rst
+++ b/libcxx/docs/ReleaseNotes/18.rst
@@ -41,7 +41,7 @@ Implemented Papers
 
 - P2497R0 - Testing for success or failure of ``<charconv>`` functions
 - P2697R1 - Interfacing ``bitset`` with ``string_view``
-
+- P2443R1 - ``views::chunk_by``
 
 Improvements and New Features
 -----------------------------

diff  --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
index 8356a986681e15b..ba26cebbc1e8a67 100644
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -49,7 +49,7 @@
 "`P2440R1 <https://wg21.link/P2440R1>`__","LWG","``ranges::iota``, ``ranges::shift_left`` and ``ranges::shift_right``","February 2022","","","|ranges|"
 "`P2441R2 <https://wg21.link/P2441R2>`__","LWG","``views::join_with``","February 2022","","","|ranges|"
 "`P2442R1 <https://wg21.link/P2442R1>`__","LWG","Windowing range adaptors: ``views::chunk`` and ``views::slide``","February 2022","","","|ranges|"
-"`P2443R1 <https://wg21.link/P2443R1>`__","LWG","``views::chunk_by``","February 2022","","","|ranges|"
+"`P2443R1 <https://wg21.link/P2443R1>`__","LWG","``views::chunk_by``","February 2022","|Complete|","18.0","|ranges|"
 "","","","","","",""
 "`P0009R18 <https://wg21.link/P0009R18>`__","LWG","mdspan: A Non-Owning Multidimensional Array Reference","July 2022","|In progress| [#note-P0009R18]_|",""
 "`P0429R9 <https://wg21.link/P0429R9>`__","LWG","A Standard ``flat_map``","July 2022","",""

diff  --git a/libcxx/docs/Status/RangesViews.csv b/libcxx/docs/Status/RangesViews.csv
index 0c1bbb8e842c2db..d7ae06932fb0c55 100644
--- a/libcxx/docs/Status/RangesViews.csv
+++ b/libcxx/docs/Status/RangesViews.csv
@@ -31,7 +31,7 @@ C++23,`adjacent_transform <https://wg21.link/P2321R2>`_,Hui Xie,No patch yet,Not
 C++23,`join_with <https://wg21.link/P2441R2>`_,Unassigned,No patch yet,Not started
 C++23,`slide <https://wg21.link/P2442R1>`_,Unassigned,No patch yet,Not started
 C++23,`chunk <https://wg21.link/P2442R1>`_,Unassigned,No patch yet,Not started
-C++23,`chunk_by <https://wg21.link/P2443R1>`_,Unassigned,No patch yet,Not started
+C++23,`chunk_by <https://wg21.link/P2443R1>`_,Jakub Mazurkiewicz,`D144767 <https://llvm.org/D144767>`,✅
 C++23,`as_const <https://wg21.link/P2278R4>`_,Unassigned,No patch yet,Not started
 C++23,`as_rvalue <https://wg21.link/P2446R2>`_,Nikolas Klauser,`D137637 <https://llvm.org/D137637>`_,✅
 C++23,`stride <https://wg21.link/P1899R3>`_,Hristo Hristov and Will Hawkins,`D156924 <https://llvm.org/D156924>`_,In Progress

diff  --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 77a7269121ec142..4fc4d23b510104e 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -602,6 +602,7 @@ set(files
   __ranges/access.h
   __ranges/all.h
   __ranges/as_rvalue_view.h
+  __ranges/chunk_by_view.h
   __ranges/common_view.h
   __ranges/concepts.h
   __ranges/container_compatible_range.h

diff  --git a/libcxx/include/__algorithm/ranges_adjacent_find.h b/libcxx/include/__algorithm/ranges_adjacent_find.h
index 5f33192666ae907..a10b04167ede697 100644
--- a/libcxx/include/__algorithm/ranges_adjacent_find.h
+++ b/libcxx/include/__algorithm/ranges_adjacent_find.h
@@ -24,6 +24,9 @@
 #  pragma GCC system_header
 #endif
 
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
 #if _LIBCPP_STD_VER >= 20
 
 _LIBCPP_BEGIN_NAMESPACE_STD
@@ -75,4 +78,6 @@ _LIBCPP_END_NAMESPACE_STD
 
 #endif // _LIBCPP_STD_VER >= 20
 
+_LIBCPP_POP_MACROS
+
 #endif // _LIBCPP___ALGORITHM_RANGES_ADJACENT_FIND_H

diff  --git a/libcxx/include/__ranges/chunk_by_view.h b/libcxx/include/__ranges/chunk_by_view.h
new file mode 100644
index 000000000000000..cfb149b443571e8
--- /dev/null
+++ b/libcxx/include/__ranges/chunk_by_view.h
@@ -0,0 +1,230 @@
+// -*- 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_CHUNK_BY_VIEW_H
+#define _LIBCPP___RANGES_CHUNK_BY_VIEW_H
+
+#include <__algorithm/ranges_adjacent_find.h>
+#include <__assert>
+#include <__concepts/constructible.h>
+#include <__config>
+#include <__functional/bind_back.h>
+#include <__functional/invoke.h>
+#include <__functional/not_fn.h>
+#include <__functional/reference_wrapper.h>
+#include <__iterator/concepts.h>
+#include <__iterator/default_sentinel.h>
+#include <__iterator/iterator_traits.h>
+#include <__iterator/next.h>
+#include <__iterator/prev.h>
+#include <__memory/addressof.h>
+#include <__ranges/access.h>
+#include <__ranges/all.h>
+#include <__ranges/concepts.h>
+#include <__ranges/movable_box.h>
+#include <__ranges/non_propagating_cache.h>
+#include <__ranges/range_adaptor.h>
+#include <__ranges/reverse_view.h>
+#include <__ranges/subrange.h>
+#include <__ranges/view_interface.h>
+#include <__type_traits/conditional.h>
+#include <__type_traits/decay.h>
+#include <__type_traits/is_nothrow_constructible.h>
+#include <__type_traits/is_object.h>
+#include <__utility/forward.h>
+#include <__utility/in_place.h>
+#include <__utility/move.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 23
+
+namespace ranges {
+
+template <forward_range _View, indirect_binary_predicate<iterator_t<_View>, iterator_t<_View>> _Pred>
+  requires view<_View> && is_object_v<_Pred>
+class chunk_by_view : public view_interface<chunk_by_view<_View, _Pred>> {
+  _LIBCPP_NO_UNIQUE_ADDRESS _View __base_ = _View();
+  _LIBCPP_NO_UNIQUE_ADDRESS __movable_box<_Pred> __pred_;
+
+  // We cache the result of begin() to allow providing an amortized O(1).
+  using _Cache = __non_propagating_cache<iterator_t<_View>>;
+  _Cache __cached_begin_;
+
+  class __iterator;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr iterator_t<_View> __find_next(iterator_t<_View> __current) {
+    _LIBCPP_ASSERT_UNCATEGORIZED(
+        __pred_.__has_value(), "Trying to call __find_next() on a chunk_by_view that does not have a valid predicate.");
+
+    return ranges::next(ranges::adjacent_find(__current, ranges::end(__base_), std::not_fn(std::ref(*__pred_))),
+                        1,
+                        ranges::end(__base_));
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr iterator_t<_View> __find_prev(iterator_t<_View> __current)
+    requires bidirectional_range<_View>
+  {
+    _LIBCPP_ASSERT_UNCATEGORIZED(
+        __current != ranges::begin(__base_), "Trying to call __find_prev() on a begin iterator.");
+    _LIBCPP_ASSERT_UNCATEGORIZED(
+        __pred_.__has_value(), "Trying to call __find_prev() on a chunk_by_view that does not have a valid predicate.");
+
+    auto __first = ranges::begin(__base_);
+    reverse_view __reversed{subrange{__first, __current}};
+    auto __reversed_pred = [this]<class _Tp, class _Up>(_Tp&& __x, _Up&& __y) {
+      return !std::invoke(*__pred_, std::forward<_Up>(__y), std::forward<_Tp>(__x));
+    };
+    return ranges::prev(ranges::adjacent_find(__reversed, __reversed_pred).base(), 1, std::move(__first));
+  }
+
+public:
+  _LIBCPP_HIDE_FROM_ABI chunk_by_view()
+    requires default_initializable<_View> && default_initializable<_Pred>
+  = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit chunk_by_view(_View __base, _Pred __pred)
+      : __base_(std::move(__base)), __pred_(in_place, std::move(__pred)) {}
+
+  _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 const _Pred& pred() const { return *__pred_; }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator begin() {
+    _LIBCPP_ASSERT_UNCATEGORIZED(
+        __pred_.__has_value(), "Trying to call begin() on a chunk_by_view that does not have a valid predicate.");
+
+    auto __first = ranges::begin(__base_);
+    if (!__cached_begin_.__has_value()) {
+      __cached_begin_.__emplace(__find_next(__first));
+    }
+    return {*this, std::move(__first), *__cached_begin_};
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr auto end() {
+    if constexpr (common_range<_View>) {
+      return __iterator{*this, ranges::end(__base_), ranges::end(__base_)};
+    } else {
+      return default_sentinel;
+    }
+  }
+};
+
+template <class _Range, class _Pred>
+chunk_by_view(_Range&&, _Pred) -> chunk_by_view<views::all_t<_Range>, _Pred>;
+
+template <forward_range _View, indirect_binary_predicate<iterator_t<_View>, iterator_t<_View>> _Pred>
+  requires view<_View> && is_object_v<_Pred>
+class chunk_by_view<_View, _Pred>::__iterator {
+  friend chunk_by_view;
+
+  chunk_by_view* __parent_                               = nullptr;
+  _LIBCPP_NO_UNIQUE_ADDRESS iterator_t<_View> __current_ = iterator_t<_View>();
+  _LIBCPP_NO_UNIQUE_ADDRESS iterator_t<_View> __next_    = iterator_t<_View>();
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator(
+      chunk_by_view& __parent, iterator_t<_View> __current, iterator_t<_View> __next)
+      : __parent_(std::addressof(__parent)), __current_(__current), __next_(__next) {}
+
+public:
+  using value_type        = subrange<iterator_t<_View>>;
+  using 
diff erence_type   = range_
diff erence_t<_View>;
+  using iterator_category = input_iterator_tag;
+  using iterator_concept  = conditional_t<bidirectional_range<_View>, bidirectional_iterator_tag, forward_iterator_tag>;
+
+  _LIBCPP_HIDE_FROM_ABI __iterator() = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr value_type operator*() const {
+    _LIBCPP_ASSERT_UNCATEGORIZED(__current_ != __next_, "Trying to dereference past-the-end chunk_by_view iterator.");
+    return {__current_, __next_};
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() {
+    _LIBCPP_ASSERT_UNCATEGORIZED(__current_ != __next_, "Trying to increment past end chunk_by_view iterator.");
+    __current_ = __next_;
+    __next_    = __parent_->__find_next(__current_);
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator++(int) {
+    auto __tmp = *this;
+    ++*this;
+    return __tmp;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator--()
+    requires bidirectional_range<_View>
+  {
+    __next_    = __current_;
+    __current_ = __parent_->__find_prev(__next_);
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator--(int)
+    requires bidirectional_range<_View>
+  {
+    auto __tmp = *this;
+    --*this;
+    return __tmp;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, const __iterator& __y) {
+    return __x.__current_ == __y.__current_;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI friend constexpr bool operator==(const __iterator& __x, default_sentinel_t) {
+    return __x.__current_ == __x.__next_;
+  }
+};
+
+namespace views {
+namespace __chunk_by {
+struct __fn {
+  template <class _Range, class _Pred>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Range&& __range, _Pred&& __pred) const
+      noexcept(noexcept(/**/ chunk_by_view(std::forward<_Range>(__range), std::forward<_Pred>(__pred))))
+          -> decltype(/*--*/ chunk_by_view(std::forward<_Range>(__range), std::forward<_Pred>(__pred))) {
+    return /*-------------*/ chunk_by_view(std::forward<_Range>(__range), std::forward<_Pred>(__pred));
+  }
+
+  template <class _Pred>
+    requires constructible_from<decay_t<_Pred>, _Pred>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Pred&& __pred) const
+      noexcept(is_nothrow_constructible_v<decay_t<_Pred>, _Pred>) {
+    return __range_adaptor_closure_t(std::__bind_back(*this, std::forward<_Pred>(__pred)));
+  }
+};
+} // namespace __chunk_by
+
+inline namespace __cpo {
+inline constexpr auto chunk_by = __chunk_by::__fn{};
+} // namespace __cpo
+} // namespace views
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___RANGES_CHUNK_BY_VIEW_H

diff  --git a/libcxx/include/__ranges/movable_box.h b/libcxx/include/__ranges/movable_box.h
index 8b3716a06c5b7d6..6615533d374349e 100644
--- a/libcxx/include/__ranges/movable_box.h
+++ b/libcxx/include/__ranges/movable_box.h
@@ -26,6 +26,9 @@
 #  pragma GCC system_header
 #endif
 
+_LIBCPP_PUSH_MACROS
+#include <__undef_macros>
+
 _LIBCPP_BEGIN_NAMESPACE_STD
 
 #if _LIBCPP_STD_VER >= 20
@@ -203,4 +206,6 @@ class __movable_box<_Tp> {
 
 _LIBCPP_END_NAMESPACE_STD
 
+_LIBCPP_POP_MACROS
+
 #endif // _LIBCPP___RANGES_MOVABLE_BOX_H

diff  --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index 3b85b6a91a21d87..e95f50dcbf05373 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -1659,6 +1659,7 @@ module std_private_ranges_all                        [system] {
   export std_private_ranges_owning_view
 }
 module std_private_ranges_as_rvalue_view             [system] { header "__ranges/as_rvalue_view.h" }
+module std_private_ranges_chunk_by_view              [system] { header "__ranages/chunk_by_view.h" }
 module std_private_ranges_common_view                [system] { header "__ranges/common_view.h" }
 module std_private_ranges_concepts                   [system] {
   header "__ranges/concepts.h"

diff  --git a/libcxx/include/ranges b/libcxx/include/ranges
index 523f7cdcb360fa3..db592fd5cb360c4 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -320,17 +320,24 @@ namespace std::ranges {
   class zip_view;        // C++23
 
   template<class... Views>
-    inline constexpr bool enable_borrowed_range<zip_view<Views...>> =    // C++23
+    inline constexpr bool enable_borrowed_range<zip_view<Views...>> =       // C++23
       (enable_borrowed_range<Views> && ...);
 
-  namespace views { inline constexpr unspecified zip = unspecified; }    // C++23
+  namespace views { inline constexpr unspecified zip = unspecified; }       // C++23
 
   // [range.as.rvalue]
   template <view V>
     requires input_range<V>
-  class as_rvalue_view; // since C++23
+  class as_rvalue_view;                                                     // C++23
 
-  namespace views { inline constexpr unspecified as_rvalue ) unspecified; } // since C++23
+  namespace views { inline constexpr unspecified as_rvalue ) unspecified; } // C++23
+
+  [range.chunk.by]
+  template<forward_range V, indirect_binary_predicate<iterator_t<V>, iterator_t<V>> Pred>
+    requires view<V> && is_object_v<Pred>
+  class chunk_by_view;                                                      // C++23
+
+  namespace views { inline constexpr unspecified chunk_by = unspecified; }  // C++23
 }
 
 namespace std {
@@ -373,6 +380,7 @@ namespace std {
 #include <__ranges/access.h>
 #include <__ranges/all.h>
 #include <__ranges/as_rvalue_view.h>
+#include <__ranges/chunk_by_view.h>
 #include <__ranges/common_view.h>
 #include <__ranges/concepts.h>
 #include <__ranges/counted.h>

diff  --git a/libcxx/include/version b/libcxx/include/version
index db6da2e0a8bd151..e5a995366a7aa48 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -435,7 +435,7 @@ __cpp_lib_within_lifetime                               202306L <type_traits>
 // # define __cpp_lib_print                                202207L
 # define __cpp_lib_ranges_as_rvalue                     202207L
 // # define __cpp_lib_ranges_chunk                         202202L
-// # define __cpp_lib_ranges_chunk_by                      202202L
+# define __cpp_lib_ranges_chunk_by                      202202L
 // # define __cpp_lib_ranges_iota                          202202L
 // # define __cpp_lib_ranges_join_with                     202202L
 # define __cpp_lib_ranges_repeat                        202207L

diff  --git a/libcxx/modules/std/ranges.inc b/libcxx/modules/std/ranges.inc
index 67b0f02ad9d980f..aeba676d3f43adc 100644
--- a/libcxx/modules/std/ranges.inc
+++ b/libcxx/modules/std/ranges.inc
@@ -309,6 +309,7 @@ export namespace std {
     namespace views {
       using std::ranges::views::slide;
     }
+#endif
 
     // [range.chunk.by], chunk by view
     using std::ranges::chunk_by_view;
@@ -317,6 +318,7 @@ export namespace std {
       using std::ranges::views::chunk_by;
     }
 
+#if 0
     // [range.stride], stride view
     using std::ranges::stride_view;
 

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/adaptor.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/adaptor.nodiscard.verify.cpp
new file mode 100644
index 000000000000000..14b3385724050de
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/adaptor.nodiscard.verify.cpp
@@ -0,0 +1,26 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// Test the libc++ extension that std::views::chunk_by is marked as [[nodiscard]].
+
+#include <functional>
+#include <ranges>
+
+void test() {
+  int range[] = {1, 2, 3, 0, 1, 2};
+  std::ranges::less_equal pred;
+
+  std::views::chunk_by(pred); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::views::chunk_by(range, pred); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  range | std::views::chunk_by(pred); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::views::all | std::views::chunk_by(pred); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+}

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.begin.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.begin.pass.cpp
new file mode 100644
index 000000000000000..0ba4745be082677
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.begin.pass.cpp
@@ -0,0 +1,35 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-exceptions
+// UNSUPPORTED: !libcpp-hardening-mode=debug
+// XFAIL: availability-verbose_abort-missing
+
+// <ranges>
+
+// Call begin() on chunk_by_view with empty predicate
+
+#include <ranges>
+
+#include "check_assertion.h"
+#include "types.h"
+
+int main(int, char**) {
+  int input[] = {1, 2, 3};
+  auto view1  = std::views::chunk_by(input, ThrowOnCopyPred{});
+  auto view2  = std::views::chunk_by(input, ThrowOnCopyPred{});
+  try {
+    view1 = view2;
+  } catch (...) {
+  }
+  TEST_LIBCPP_ASSERT_FAILURE(
+      view1.begin(), "Trying to call begin() on a chunk_by_view that does not have a valid predicate.");
+  return 0;
+}

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.find-next.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.find-next.pass.cpp
new file mode 100644
index 000000000000000..9e5536bf51be2bb
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.find-next.pass.cpp
@@ -0,0 +1,38 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-exceptions
+// UNSUPPORTED: !libcpp-hardening-mode=debug
+// XFAIL: availability-verbose_abort-missing
+
+// <ranges>
+
+// Call find-next() on chunk_by_view with empty predicate
+
+#include <ranges>
+
+#include "check_assertion.h"
+#include "types.h"
+
+int main(int, char**) {
+  int input[] = {1, 2, 3};
+  // This is the easiest way to get '__find_next' to fail. If we used default constructed view here,
+  // then begin() would fail instead of __find_next.
+  auto view1 = std::views::chunk_by(input, ThrowOnCopyPred{});
+  auto view2 = std::views::chunk_by(input, ThrowOnCopyPred{});
+  auto it    = view1.begin();
+  try {
+    view1 = view2;
+  } catch (...) {
+  }
+  TEST_LIBCPP_ASSERT_FAILURE(
+      ++it, "Trying to call __find_next() on a chunk_by_view that does not have a valid predicate.");
+  return 0;
+}

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.find-prev.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.find-prev.pass.cpp
new file mode 100644
index 000000000000000..4f746cfec2f1a49
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/assert.find-prev.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
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: no-exceptions
+// UNSUPPORTED: !libcpp-hardening-mode=debug
+// XFAIL: availability-verbose_abort-missing
+
+// <ranges>
+
+// Call find-prev() on chunk_by_view with begin iterator
+// Call find-prev() on chunk_by_view with empty predicate
+
+#include <functional>
+#include <ranges>
+
+#include "check_assertion.h"
+#include "types.h"
+
+int main(int, char**) {
+  int input[] = {1, 1, 2, 2};
+
+  { // Call find-prev() on chunk_by_view with begin iterator
+    auto view = std::views::chunk_by(input, std::equal_to{});
+    auto it   = view.begin();
+    TEST_LIBCPP_ASSERT_FAILURE(--it, "Trying to call __find_prev() on a begin iterator.");
+  }
+
+  { // Call find-prev() on chunk_by_view with empty predicate
+    auto view1 = std::views::chunk_by(input, ThrowOnCopyPred{});
+    auto view2 = std::views::chunk_by(input, ThrowOnCopyPred{});
+    auto it    = view1.begin();
+    ++it;
+    try {
+      view1 = view2;
+    } catch (...) {
+    }
+    TEST_LIBCPP_ASSERT_FAILURE(
+        --it, "Trying to call __find_prev() on a chunk_by_view that does not have a valid predicate.");
+  }
+
+  return 0;
+}

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/assert.deref.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/assert.deref.pass.cpp
new file mode 100644
index 000000000000000..80c08efc56c05e0
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/assert.deref.pass.cpp
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: !libcpp-hardening-mode=debug
+// XFAIL: availability-verbose_abort-missing
+
+// <ranges>
+
+// Dereference past end chunk_by_view iterator
+
+#include <functional>
+#include <ranges>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  int input[] = {1, 2, 3};
+  auto view   = std::views::chunk_by(input, std::less{});
+  auto it     = view.begin();
+  ++it;
+  TEST_LIBCPP_ASSERT_FAILURE(*it, "Trying to dereference past-the-end chunk_by_view iterator.");
+  return 0;
+}

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/assert.increment.pass.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/assert.increment.pass.cpp
new file mode 100644
index 000000000000000..7381ad288629399
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/assert.increment.pass.cpp
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: has-unix-headers
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// UNSUPPORTED: !libcpp-hardening-mode=debug
+// XFAIL: availability-verbose_abort-missing
+
+// <ranges>
+
+// Increment past end chunk_by_view iterator
+
+#include <functional>
+#include <ranges>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  int input[] = {1, 2, 3};
+  auto view   = std::views::chunk_by(input, std::less{});
+  auto it     = view.begin();
+  ++it;
+  TEST_LIBCPP_ASSERT_FAILURE(++it, "Trying to increment past end chunk_by_view iterator.");
+  return 0;
+}

diff  --git a/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/types.h b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/types.h
new file mode 100644
index 000000000000000..88fbcefab4d7410
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk.by/types.h
@@ -0,0 +1,23 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_CHUNK_BY_TYPES_H
+#define TEST_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_CHUNK_BY_TYPES_H
+
+struct ThrowOnCopyPred {
+  ThrowOnCopyPred() = default;
+  ThrowOnCopyPred(const ThrowOnCopyPred&) { throw 0; }
+  ThrowOnCopyPred& operator=(const ThrowOnCopyPred&) = delete;
+
+  ThrowOnCopyPred(ThrowOnCopyPred&&)            = default;
+  ThrowOnCopyPred& operator=(ThrowOnCopyPred&&) = default;
+
+  bool operator()(int x, int y) const { return x != y; }
+};
+
+#endif // TEST_LIBCXX_RANGES_RANGE_ADAPTORS_RANGE_CHUNK_BY_TYPES_H

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx03.csv b/libcxx/test/libcxx/transitive_includes/cxx03.csv
index 02976e87092e4be..7d7696fcd4f8c03 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx03.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx03.csv
@@ -687,11 +687,11 @@ ranges initializer_list
 ranges iosfwd
 ranges iterator
 ranges limits
+ranges new
 ranges optional
 ranges span
 ranges tuple
 ranges type_traits
-ranges variant
 ranges version
 ratio climits
 ratio cstdint

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx11.csv b/libcxx/test/libcxx/transitive_includes/cxx11.csv
index e51c9be04a26cd6..63eb7cc18c54c11 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx11.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx11.csv
@@ -692,11 +692,11 @@ ranges initializer_list
 ranges iosfwd
 ranges iterator
 ranges limits
+ranges new
 ranges optional
 ranges span
 ranges tuple
 ranges type_traits
-ranges variant
 ranges version
 ratio climits
 ratio cstdint

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx14.csv b/libcxx/test/libcxx/transitive_includes/cxx14.csv
index 880b97a61deb918..b1e05b70a3a45cb 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx14.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx14.csv
@@ -694,11 +694,11 @@ ranges initializer_list
 ranges iosfwd
 ranges iterator
 ranges limits
+ranges new
 ranges optional
 ranges span
 ranges tuple
 ranges type_traits
-ranges variant
 ranges version
 ratio climits
 ratio cstdint

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx17.csv b/libcxx/test/libcxx/transitive_includes/cxx17.csv
index 880b97a61deb918..b1e05b70a3a45cb 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx17.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx17.csv
@@ -694,11 +694,11 @@ ranges initializer_list
 ranges iosfwd
 ranges iterator
 ranges limits
+ranges new
 ranges optional
 ranges span
 ranges tuple
 ranges type_traits
-ranges variant
 ranges version
 ratio climits
 ratio cstdint

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx20.csv b/libcxx/test/libcxx/transitive_includes/cxx20.csv
index b04506293513113..b82629137dc14df 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx20.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx20.csv
@@ -700,11 +700,11 @@ ranges initializer_list
 ranges iosfwd
 ranges iterator
 ranges limits
+ranges new
 ranges optional
 ranges span
 ranges tuple
 ranges type_traits
-ranges variant
 ranges version
 ratio climits
 ratio cstdint

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx23.csv b/libcxx/test/libcxx/transitive_includes/cxx23.csv
index bd3ab60f5756c67..524eb674f3b5e0f 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx23.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx23.csv
@@ -499,6 +499,7 @@ ranges initializer_list
 ranges iosfwd
 ranges iterator
 ranges limits
+ranges new
 ranges optional
 ranges span
 ranges tuple

diff  --git a/libcxx/test/libcxx/transitive_includes/cxx26.csv b/libcxx/test/libcxx/transitive_includes/cxx26.csv
index bd3ab60f5756c67..524eb674f3b5e0f 100644
--- a/libcxx/test/libcxx/transitive_includes/cxx26.csv
+++ b/libcxx/test/libcxx/transitive_includes/cxx26.csv
@@ -499,6 +499,7 @@ ranges initializer_list
 ranges iosfwd
 ranges iterator
 ranges limits
+ranges new
 ranges optional
 ranges span
 ranges tuple

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
index 4a439ae90fcc0f3..582e0c7dfe5e65d 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/ranges.version.compile.pass.cpp
@@ -214,17 +214,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_chunk_by
-#     error "__cpp_lib_ranges_chunk_by should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_chunk_by != 202202L
-#     error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_chunk_by
-#     error "__cpp_lib_ranges_chunk_by should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_chunk_by
+#   error "__cpp_lib_ranges_chunk_by should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_chunk_by != 202202L
+#   error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++23"
 # endif
 
 # if !defined(_LIBCPP_VERSION)
@@ -309,17 +303,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_chunk_by
-#     error "__cpp_lib_ranges_chunk_by should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_chunk_by != 202202L
-#     error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_chunk_by
-#     error "__cpp_lib_ranges_chunk_by should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_chunk_by
+#   error "__cpp_lib_ranges_chunk_by should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_chunk_by != 202202L
+#   error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++26"
 # endif
 
 # if !defined(_LIBCPP_VERSION)

diff  --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index 8903913027db73f..566c595d8c30823 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -4971,17 +4971,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_chunk_by
-#     error "__cpp_lib_ranges_chunk_by should be defined in c++23"
-#   endif
-#   if __cpp_lib_ranges_chunk_by != 202202L
-#     error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++23"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_chunk_by
-#     error "__cpp_lib_ranges_chunk_by should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_chunk_by
+#   error "__cpp_lib_ranges_chunk_by should be defined in c++23"
+# endif
+# if __cpp_lib_ranges_chunk_by != 202202L
+#   error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++23"
 # endif
 
 # if !defined(_LIBCPP_VERSION)
@@ -6518,17 +6512,11 @@
 #   endif
 # endif
 
-# if !defined(_LIBCPP_VERSION)
-#   ifndef __cpp_lib_ranges_chunk_by
-#     error "__cpp_lib_ranges_chunk_by should be defined in c++26"
-#   endif
-#   if __cpp_lib_ranges_chunk_by != 202202L
-#     error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++26"
-#   endif
-# else // _LIBCPP_VERSION
-#   ifdef __cpp_lib_ranges_chunk_by
-#     error "__cpp_lib_ranges_chunk_by should not be defined because it is unimplemented in libc++!"
-#   endif
+# ifndef __cpp_lib_ranges_chunk_by
+#   error "__cpp_lib_ranges_chunk_by should be defined in c++26"
+# endif
+# if __cpp_lib_ranges_chunk_by != 202202L
+#   error "__cpp_lib_ranges_chunk_by should have the value 202202L in c++26"
 # endif
 
 # if !defined(_LIBCPP_VERSION)

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/adaptor.pass.cpp
new file mode 100644
index 000000000000000..423c481127e20e8
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/adaptor.pass.cpp
@@ -0,0 +1,237 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// std::views::chunk_by
+
+#include <ranges>
+
+#include <algorithm>
+#include <cassert>
+#include <concepts>
+#include <initializer_list>
+#include <type_traits>
+#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 Pred {
+  constexpr bool operator()(int x, int y) const { return x != -y; }
+};
+
+struct NonCopyablePredicate : Pred {
+  NonCopyablePredicate(NonCopyablePredicate const&) = delete;
+};
+
+struct Range : std::ranges::view_base {
+  using Iterator = forward_iterator<int*>;
+  using Sentinel = sentinel_wrapper<Iterator>;
+  constexpr explicit Range(int* b, int* e) : begin_(b), end_(e) {}
+  constexpr Iterator begin() const { return Iterator(begin_); }
+  constexpr Sentinel end() const { return Sentinel(Iterator(end_)); }
+
+private:
+  int* begin_;
+  int* end_;
+};
+
+template <typename View>
+constexpr void compareViews(View v, std::initializer_list<std::initializer_list<int>> list) {
+  auto b1 = v.begin();
+  auto e1 = v.end();
+  auto b2 = list.begin();
+  auto e2 = list.end();
+  for (; b1 != e1 && b2 != e2; ++b1, ++b2) {
+    bool eq = std::ranges::equal(*b1, *b2, [](int x, int y) {
+      assert(x == y);
+      return true;
+    });
+    assert(eq);
+  }
+  assert(b1 == e1);
+  assert(b2 == e2);
+}
+
+constexpr int absoluteValue(int x) { return x < 0 ? -x : x; }
+
+template <class T>
+constexpr const T&& asConstRvalue(T&& t) {
+  return static_cast<T const&&>(t);
+}
+
+constexpr bool test() {
+  int buff[] = {-4, -3, -2, -1, 1, 2, 3, 4};
+
+  // Test range adaptor object
+  {
+    using RangeAdaptorObject = decltype(std::views::chunk_by);
+    static_assert(std::is_const_v<RangeAdaptorObject>);
+
+    // The type of a customization point object, ignoring cv-qualifiers, shall model semiregular
+    static_assert(std::semiregular<std::remove_const<RangeAdaptorObject>>);
+  }
+
+  // Test `views::chunk_by(pred)(v)`
+  {
+    using Result = std::ranges::chunk_by_view<Range, Pred>;
+    Range const range(buff, buff + 8);
+    Pred pred;
+
+    {
+      // 'views::chunk_by(pred)' - &&
+      std::same_as<Result> decltype(auto) result = std::views::chunk_by(pred)(range);
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+    {
+      // 'views::chunk_by(pred)' - const&&
+      std::same_as<Result> decltype(auto) result = asConstRvalue(std::views::chunk_by(pred))(range);
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+    {
+      // 'views::chunk_by(pred)' - &
+      auto partial                               = std::views::chunk_by(pred);
+      std::same_as<Result> decltype(auto) result = partial(range);
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+    {
+      // 'views::chunk_by(pred)' - const&
+      auto const partial                         = std::views::chunk_by(pred);
+      std::same_as<Result> decltype(auto) result = partial(range);
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+  }
+
+  // Test `v | views::chunk_by(pred)`
+  {
+    using Result = std::ranges::chunk_by_view<Range, Pred>;
+    Range const range(buff, buff + 8);
+    Pred pred;
+
+    {
+      // 'views::chunk_by(pred)' - &&
+      std::same_as<Result> decltype(auto) result = range | std::views::chunk_by(pred);
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+    {
+      // 'views::chunk_by(pred)' - const&&
+      std::same_as<Result> decltype(auto) result = range | asConstRvalue(std::views::chunk_by(pred));
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+    {
+      // 'views::chunk_by(pred)' - &
+      auto partial                               = std::views::chunk_by(pred);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+    {
+      // 'views::chunk_by(pred)' - const&
+      auto const partial                         = std::views::chunk_by(pred);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+  }
+
+  // Test `views::chunk_by(v, pred)` range adaptor object
+  {
+    using Result = std::ranges::chunk_by_view<Range, Pred>;
+    Range const range(buff, buff + 8);
+    Pred pred;
+
+    {
+      // 'views::chunk_by' - &&
+      auto range_adaptor                         = std::views::chunk_by;
+      std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, pred);
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+    {
+      // 'views::chunk_by' - const&&
+      auto const range_adaptor                   = std::views::chunk_by;
+      std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, pred);
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+    {
+      // 'views::chunk_by' - &
+      auto range_adaptor                         = std::views::chunk_by;
+      std::same_as<Result> decltype(auto) result = range_adaptor(range, pred);
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+    {
+      // 'views::chunk_by' - const&
+      auto const range_adaptor                   = std::views::chunk_by;
+      std::same_as<Result> decltype(auto) result = range_adaptor(range, pred);
+      compareViews(result, {{-4, -3, -2, -1}, {1, 2, 3, 4}});
+    }
+  }
+
+  // Test that one can call std::views::chunk_by 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.
+  {
+    int array[3]                  = {1, 2, 3};
+    [[maybe_unused]] auto partial = std::views::chunk_by(std::move(array));
+  }
+
+  // Test `adaptor | views::chunk_by(pred)`
+  {
+    Range const range(buff, buff + 8);
+
+    {
+      auto pred1 = [](int i) { return absoluteValue(i) < 3; };
+      Pred pred2;
+      using Result = std::ranges::chunk_by_view<std::ranges::filter_view<Range, decltype(pred1)>, Pred>;
+      std::same_as<Result> decltype(auto) result = range | std::views::filter(pred1) | std::views::chunk_by(pred2);
+      compareViews(result, {{-2, -1}, {1, 2}});
+    }
+    {
+      auto pred1 = [](int i) { return absoluteValue(i) < 3; };
+      Pred pred2;
+      using Result       = std::ranges::chunk_by_view<std::ranges::filter_view<Range, decltype(pred1)>, Pred>;
+      auto const partial = std::views::filter(pred1) | std::views::chunk_by(pred2);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compareViews(result, {{-2, -1}, {1, 2}});
+    }
+  }
+
+  // Test SFINAE friendliness
+  {
+    struct NotAView {};
+    struct NotInvocable {};
+
+    static_assert(!CanBePiped<Range, decltype(std::views::chunk_by)>);
+    static_assert(CanBePiped<Range, decltype(std::views::chunk_by(Pred{}))>);
+    static_assert(!CanBePiped<NotAView, decltype(std::views::chunk_by(Pred{}))>);
+    static_assert(!CanBePiped<std::initializer_list<int>, decltype(std::views::chunk_by(Pred{}))>);
+    static_assert(!CanBePiped<Range, decltype(std::views::chunk_by(NotInvocable{}))>);
+
+    static_assert(!std::is_invocable_v<decltype(std::views::chunk_by)>);
+    static_assert(!std::is_invocable_v<decltype(std::views::chunk_by), Pred, Range>);
+    static_assert(std::is_invocable_v<decltype(std::views::chunk_by), Range, Pred>);
+    static_assert(!std::is_invocable_v<decltype(std::views::chunk_by), Range, Pred, Pred>);
+    static_assert(!std::is_invocable_v<decltype(std::views::chunk_by), NonCopyablePredicate>);
+  }
+
+  { static_assert(std::is_same_v<decltype(std::ranges::views::chunk_by), decltype(std::views::chunk_by)>); }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/base.pass.cpp
new file mode 100644
index 000000000000000..cc3fcfd96fbec81
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/base.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, c++20
+
+// <ranges>
+
+// constexpr View base() const& requires copy_constructible<View>;
+// constexpr View base() &&;
+
+#include <ranges>
+
+#include <cassert>
+#include <concepts>
+#include <utility>
+
+struct Range : std::ranges::view_base {
+  constexpr explicit Range(int* b, int* e) : begin_(b), end_(e) {}
+  constexpr Range(Range const& other) : begin_(other.begin_), end_(other.end_), wasCopyInitialized(true) {}
+  constexpr Range(Range&& other) : begin_(other.begin_), end_(other.end_), wasMoveInitialized(true) {}
+  Range& operator=(Range const&) = default;
+  Range& operator=(Range&&)      = default;
+  constexpr int* begin() const { return begin_; }
+  constexpr int* end() const { return end_; }
+
+  int* begin_;
+  int* end_;
+  bool wasCopyInitialized = false;
+  bool wasMoveInitialized = false;
+};
+
+static_assert(std::ranges::view<Range>);
+static_assert(std::ranges::forward_range<Range>);
+
+struct Pred {
+  bool operator()(int, int) const;
+};
+
+struct NonCopyableRange : std::ranges::view_base {
+  explicit NonCopyableRange(int*, int*);
+  NonCopyableRange(NonCopyableRange const&)            = delete;
+  NonCopyableRange(NonCopyableRange&&)                 = default;
+  NonCopyableRange& operator=(NonCopyableRange const&) = default;
+  NonCopyableRange& operator=(NonCopyableRange&&)      = default;
+  int* begin() const;
+  int* end() const;
+};
+
+static_assert(!std::copy_constructible<NonCopyableRange>);
+
+template <typename T>
+concept CanCallBaseOn = requires(T t) { std::forward<T>(t).base(); };
+
+constexpr bool test() {
+  int buff[] = {1, 2, 3, 4};
+
+  // Check the const& overload
+  {
+    Range range(buff, buff + 4);
+    std::ranges::chunk_by_view<Range, Pred> const view(range, Pred{});
+    std::same_as<Range> decltype(auto) result = view.base();
+    assert(result.wasCopyInitialized);
+    assert(result.begin() == buff);
+    assert(result.end() == buff + 4);
+  }
+
+  // Check the && overload
+  {
+    Range range(buff, buff + 4);
+    std::ranges::chunk_by_view<Range, Pred> view(range, Pred{});
+    std::same_as<Range> decltype(auto) result = std::move(view).base();
+    assert(result.wasMoveInitialized);
+    assert(result.begin() == buff);
+    assert(result.end() == buff + 4);
+  }
+
+  // Ensure the const& overload is not considered when the base is not copy-constructible
+  {
+    static_assert(!CanCallBaseOn<std::ranges::chunk_by_view<NonCopyableRange, Pred> const&>);
+    static_assert(!CanCallBaseOn<std::ranges::chunk_by_view<NonCopyableRange, Pred>&>);
+    static_assert(!CanCallBaseOn<std::ranges::chunk_by_view<NonCopyableRange, Pred> const&&>);
+    static_assert(CanCallBaseOn<std::ranges::chunk_by_view<NonCopyableRange, Pred>&&>);
+    static_assert(CanCallBaseOn<std::ranges::chunk_by_view<NonCopyableRange, Pred>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/begin.pass.cpp
new file mode 100644
index 000000000000000..d2d1d313cebe939
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/begin.pass.cpp
@@ -0,0 +1,156 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// constexpr iterator begin();
+
+#include <ranges>
+
+#include <cassert>
+#include <utility>
+
+#include "test_iterators.h"
+#include "types.h"
+
+struct Range : std::ranges::view_base {
+  using Iterator = forward_iterator<int*>;
+  using Sentinel = sentinel_wrapper<Iterator>;
+  constexpr explicit Range(int* b, int* e) : begin_(b), end_(e) {}
+  constexpr Iterator begin() const { return Iterator(begin_); }
+  constexpr Sentinel end() const { return Sentinel(Iterator(end_)); }
+
+private:
+  int* begin_;
+  int* end_;
+};
+
+struct TrackingPred : TrackInitialization {
+  using TrackInitialization::TrackInitialization;
+  constexpr bool operator()(int x, int y) { return x != -y; }
+};
+
+template <class T>
+concept HasBegin = requires(T t) { t.begin(); };
+
+static_assert(HasBegin<std::ranges::chunk_by_view<Range, TrackingPred>>);
+static_assert(!HasBegin<const std::ranges::chunk_by_view<Range, TrackingPred>>);
+
+constexpr bool test() {
+  int buff[] = {-4, -3, -2, -1, 1, 2, 3, 4};
+
+  // Check the return type of `begin()`
+  {
+    Range range(buff, buff + 1);
+    auto pred = [](int, int) { return true; };
+    std::ranges::chunk_by_view view(range, pred);
+    using ChunkByIterator = std::ranges::iterator_t<decltype(view)>;
+    ASSERT_SAME_TYPE(ChunkByIterator, decltype(view.begin()));
+  }
+
+  // begin() over an empty range
+  {
+    Range range(buff, buff);
+    auto pred = [](int, int) { return true; };
+    std::ranges::chunk_by_view view(range, pred);
+    auto it = view.begin();
+    assert(it == view.begin());
+    assert(it == view.end());
+  }
+
+  // begin() over a 1-element range
+  {
+    Range range(buff, buff + 1);
+    auto pred = [](int x, int y) { return x == y; };
+    std::ranges::chunk_by_view view(range, pred);
+    auto it = view.begin();
+    assert(base((*it).begin()) == buff);
+    assert(base((*it).end()) == buff + 1);
+  }
+
+  // begin() over a 2-element range
+  {
+    Range range(buff, buff + 2);
+    auto pred = [](int x, int y) { return x == y; };
+    std::ranges::chunk_by_view view(range, pred);
+    auto it = view.begin();
+    assert(base((*it).begin()) == buff);
+    assert(base((*it).end()) == buff + 1);
+    assert(base((*++it).begin()) == buff + 1);
+    assert(base((*it).end()) == buff + 2);
+  }
+
+  // begin() over a longer range
+  {
+    Range range(buff, buff + 8);
+    auto pred = [](int x, int y) { return x != -y; };
+    std::ranges::chunk_by_view view(range, pred);
+    auto it = view.begin();
+    assert(base((*it).end()) == buff + 4);
+  }
+
+  // Make sure we do not make a copy of the predicate when we call begin()
+  // (we should be passing it to ranges::adjacent_find using std::ref)
+  {
+    bool moved = false, copied = false;
+    Range range(buff, buff + 2);
+    std::ranges::chunk_by_view view(range, TrackingPred(&moved, &copied));
+    std::exchange(moved, false);
+    [[maybe_unused]] auto it = view.begin();
+    assert(!moved);
+    assert(!copied);
+  }
+
+  // Test with a non-const predicate
+  {
+    Range range(buff, buff + 8);
+    auto pred = [](int x, int y) mutable { return x != -y; };
+    std::ranges::chunk_by_view view(range, pred);
+    auto it = view.begin();
+    assert(base((*it).end()) == buff + 4);
+  }
+
+  // Test with a predicate that takes by non-const reference
+  {
+    Range range(buff, buff + 8);
+    auto pred = [](int& x, int& y) { return x != -y; };
+    std::ranges::chunk_by_view view(range, pred);
+    auto it = view.begin();
+    assert(base((*it).end()) == buff + 4);
+  }
+
+  // Test caching
+  {
+    // Make sure that we cache the result of begin() on subsequent calls
+    Range range(buff, buff + 8);
+    int called = 0;
+    auto pred  = [&](int x, int y) {
+      ++called;
+      return x != -y;
+    };
+
+    std::ranges::chunk_by_view view(range, pred);
+    assert(called == 0);
+    for (int k = 0; k != 3; ++k) {
+      auto it = view.begin();
+      assert(base((*it).end()) == buff + 4);
+      assert(called == 4); // 4, because the cached iterator is 'buff + 4' (end of the first chunk)
+    }
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/constraints.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/constraints.compile.pass.cpp
new file mode 100644
index 000000000000000..9054fea08c07500
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/constraints.compile.pass.cpp
@@ -0,0 +1,121 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// Check constraints on the type itself.
+//
+// template <forward_range View, indirect_binary_predicate<iterator_t<View>, iterator_t<View>> Pred>
+//   requires view<View> && is_object_v<Pred>
+// class chunk_by_view;
+
+#include <ranges>
+
+#include <concepts>
+#include <cstddef>
+#include <iterator>
+#include <type_traits>
+
+#include "almost_satisfies_types.h"
+#include "test_iterators.h"
+
+template <class View, class Pred>
+concept CanFormChunkByView = requires { typename std::ranges::chunk_by_view<View, Pred>; };
+
+// chunk_by_view is not valid when the view is not a forward_range
+namespace test_when_view_is_not_a_forward_range {
+
+struct View : std::ranges::view_base {
+  ForwardIteratorNotDerivedFrom begin() const;
+  ForwardIteratorNotDerivedFrom end() const;
+};
+struct Pred {
+  bool operator()(int, int) const;
+};
+
+static_assert(!std::ranges::forward_range<View>);
+static_assert(std::indirect_binary_predicate<Pred, int*, int*>);
+static_assert(std::ranges::view<View>);
+static_assert(std::is_object_v<Pred>);
+static_assert(!CanFormChunkByView<View, Pred>);
+
+} // namespace test_when_view_is_not_a_forward_range
+
+// chunk_by_view is not valid when the predicate is not indirect_binary_predicate
+namespace test_when_the_predicate_is_not_indirect_binary_predicate {
+
+struct View : std::ranges::view_base {
+  int* begin() const;
+  int* end() const;
+};
+struct Pred {};
+
+static_assert(std::ranges::forward_range<View>);
+static_assert(!std::indirect_binary_predicate<Pred, int*, int*>);
+static_assert(std::ranges::view<View>);
+static_assert(std::is_object_v<Pred>);
+static_assert(!CanFormChunkByView<View, Pred>);
+
+} // namespace test_when_the_predicate_is_not_indirect_binary_predicate
+
+// chunk_by_view is not valid when the view is not a view
+namespace test_when_the_view_param_is_not_a_view {
+
+struct View {
+  int* begin() const;
+  int* end() const;
+};
+struct Pred {
+  bool operator()(int, int) const;
+};
+
+static_assert(std::ranges::input_range<View>);
+static_assert(std::indirect_binary_predicate<Pred, int*, int*>);
+static_assert(!std::ranges::view<View>);
+static_assert(std::is_object_v<Pred>);
+static_assert(!CanFormChunkByView<View, Pred>);
+
+} // namespace test_when_the_view_param_is_not_a_view
+
+// chunk_by_view is not valid when the predicate is not an object type
+namespace test_when_the_predicate_is_not_an_object_type {
+
+struct View : std::ranges::view_base {
+  int* begin() const;
+  int* end() const;
+};
+using Pred = bool (&)(int, int);
+
+static_assert(std::ranges::input_range<View>);
+static_assert(std::indirect_binary_predicate<Pred, int*, int*>);
+static_assert(std::ranges::view<View>);
+static_assert(!std::is_object_v<Pred>);
+static_assert(!CanFormChunkByView<View, Pred>);
+
+} // namespace test_when_the_predicate_is_not_an_object_type
+
+// chunk_by_view is valid when all the constraints are satisfied (test the test)
+namespace test_when_all_the_constraints_are_satisfied {
+
+struct View : std::ranges::view_base {
+  int* begin() const;
+  int* end() const;
+};
+struct Pred {
+  bool operator()(int, int) const;
+};
+
+static_assert(std::ranges::input_range<View>);
+static_assert(std::indirect_binary_predicate<Pred, int*, int*>);
+static_assert(std::ranges::view<View>);
+static_assert(std::is_object_v<Pred>);
+static_assert(CanFormChunkByView<View, Pred>);
+
+} // namespace test_when_all_the_constraints_are_satisfied

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctad.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctad.pass.cpp
new file mode 100644
index 000000000000000..9df292844f1899d
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctad.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, c++20
+
+// <ranges>
+
+// template <class Range, class Pred>
+// chunk_by_view(Range&&, Pred) -> chunk_by_view<views::all_t<Range>, Pred>;
+
+#include <ranges>
+
+#include <cassert>
+#include <type_traits>
+
+#include "test_iterators.h"
+
+struct View : std::ranges::view_base {
+  View() = default;
+  forward_iterator<int*> begin() const;
+  sentinel_wrapper<forward_iterator<int*>> end() const;
+};
+static_assert(std::ranges::view<View>);
+
+// A range that is not a view
+struct Range {
+  Range() = default;
+  forward_iterator<int*> begin() const;
+  sentinel_wrapper<forward_iterator<int*>> end() const;
+};
+static_assert(std::ranges::range<Range>);
+static_assert(!std::ranges::view<Range>);
+
+struct Pred {
+  constexpr bool operator()(int x, int y) const { return x <= y; }
+};
+
+constexpr bool test() {
+  {
+    View v;
+    Pred pred;
+    std::ranges::chunk_by_view view(v, pred);
+    static_assert(std::is_same_v<decltype(view), std::ranges::chunk_by_view<View, Pred>>);
+  }
+  {
+    Range r;
+    Pred pred;
+    std::ranges::chunk_by_view view(r, pred);
+    static_assert(std::is_same_v<decltype(view), std::ranges::chunk_by_view<std::ranges::ref_view<Range>, Pred>>);
+  }
+  {
+    Pred pred;
+    std::ranges::chunk_by_view view(Range{}, pred);
+    static_assert(std::is_same_v<decltype(view), std::ranges::chunk_by_view<std::ranges::owning_view<Range>, Pred>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctor.default.pass.cpp
new file mode 100644
index 000000000000000..98c6cb7af5f5633
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctor.default.pass.cpp
@@ -0,0 +1,132 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// chunk_by_view() requires std::default_initializable<View> &&
+//                          std::default_initializable<Pred> = default;
+
+#include <ranges>
+
+#include <cassert>
+#include <type_traits>
+
+constexpr int buff[] = {-2, 1, -1, 2};
+
+struct DefaultConstructibleView : std::ranges::view_base {
+  DefaultConstructibleView() = default;
+  constexpr int const* begin() const { return buff; }
+  constexpr int const* end() const { return buff + 4; }
+};
+
+struct DefaultConstructiblePredicate {
+  DefaultConstructiblePredicate() = default;
+  constexpr bool operator()(int x, int y) const { return x != -y; }
+};
+
+struct NoDefaultView : std::ranges::view_base {
+  NoDefaultView() = delete;
+  int* begin() const;
+  int* end() const;
+};
+
+struct NoDefaultPredicate {
+  NoDefaultPredicate() = delete;
+  constexpr bool operator()(int, int) const;
+};
+
+struct NoexceptView : std::ranges::view_base {
+  NoexceptView() noexcept;
+  int const* begin() const;
+  int const* end() const;
+};
+
+struct NoexceptPredicate {
+  NoexceptPredicate() noexcept;
+  bool operator()(int, int) const;
+};
+
+struct MayThrowView : std::ranges::view_base {
+  MayThrowView() noexcept(false);
+  int const* begin() const;
+  int const* end() const;
+};
+
+struct MayThrowPredicate {
+  MayThrowPredicate() noexcept(false);
+  bool operator()(int, int) const;
+};
+
+constexpr void compareRanges(std::ranges::subrange<const int*> v, std::initializer_list<int> list) {
+  assert(v.size() == list.size());
+  for (size_t i = 0; i < v.size(); ++i) {
+    assert(v[i] == list.begin()[i]);
+  }
+}
+
+constexpr bool test() {
+  // Check default constructor with default initialization
+  {
+    using View = std::ranges::chunk_by_view<DefaultConstructibleView, DefaultConstructiblePredicate>;
+    View view;
+    auto it = view.begin(), end = view.end();
+    compareRanges(*it++, {-2, 1});
+    compareRanges(*it++, {-1, 2});
+    assert(it == end);
+  }
+
+  // Check default construction with copy-list-initialization
+  {
+    using View = std::ranges::chunk_by_view<DefaultConstructibleView, DefaultConstructiblePredicate>;
+    View view  = {};
+    auto it = view.begin(), end = view.end();
+    compareRanges(*it++, {-2, 1});
+    compareRanges(*it++, {-1, 2});
+    assert(it == end);
+  }
+
+  // Check cases where the default constructor isn't provided
+  {
+    static_assert(
+        !std::is_default_constructible_v<std::ranges::chunk_by_view<NoDefaultView, DefaultConstructiblePredicate>>);
+    static_assert(
+        !std::is_default_constructible_v<std::ranges::chunk_by_view<DefaultConstructibleView, NoDefaultPredicate>>);
+    static_assert(!std::is_default_constructible_v<std::ranges::chunk_by_view<NoDefaultView, NoDefaultPredicate>>);
+  }
+
+  // Check noexcept-ness
+  {
+    {
+      using View = std::ranges::chunk_by_view<MayThrowView, MayThrowPredicate>;
+      static_assert(!noexcept(View()));
+    }
+    {
+      using View = std::ranges::chunk_by_view<MayThrowView, NoexceptPredicate>;
+      static_assert(!noexcept(View()));
+    }
+    {
+      using View = std::ranges::chunk_by_view<NoexceptView, MayThrowPredicate>;
+      static_assert(!noexcept(View()));
+    }
+    {
+      using View = std::ranges::chunk_by_view<NoexceptView, NoexceptPredicate>;
+      static_assert(noexcept(View()));
+    }
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctor.view_pred.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctor.view_pred.pass.cpp
new file mode 100644
index 000000000000000..9730a851f3dac8c
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/ctor.view_pred.pass.cpp
@@ -0,0 +1,109 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// constexpr chunk_by_view(View, Pred);
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <utility>
+
+#include "types.h"
+
+struct Range : std::ranges::view_base {
+  constexpr explicit Range(int* b, int* e) : begin_(b), end_(e) {}
+  constexpr int* begin() const { return begin_; }
+  constexpr int* end() const { return end_; }
+
+private:
+  int* begin_;
+  int* end_;
+};
+
+static_assert(std::ranges::view<Range>);
+static_assert(std::ranges::forward_range<Range>);
+
+struct Pred {
+  constexpr bool operator()(int x, int y) const { return x <= y; }
+};
+
+struct TrackingPred : TrackInitialization {
+  using TrackInitialization::TrackInitialization;
+  constexpr bool operator()(int&, int&) const;
+};
+
+struct TrackingRange : TrackInitialization, std::ranges::view_base {
+  using TrackInitialization::TrackInitialization;
+  int* begin() const;
+  int* end() const;
+};
+
+template <class T>
+void implicitConstructionTest(T);
+
+template <class T, class... Args>
+concept ImplicitConstructibleFrom =
+    requires(Args&&... args) { implicitConstructionTest({std::forward<Args>(args)...}); };
+
+constexpr bool test() {
+  int buff[] = {1, 2, 3, 0, 1, 2, -1, -1, 0};
+
+  // Test explicit syntax
+  {
+    Range range(buff, buff + 9);
+    Pred pred;
+    std::ranges::chunk_by_view<Range, Pred> view(range, pred);
+    auto it = view.begin(), end = view.end();
+    assert(std::ranges::equal(*it++, std::array{1, 2, 3}));
+    assert(std::ranges::equal(*it++, std::array{0, 1, 2}));
+    assert(std::ranges::equal(*it++, std::array{-1, -1, 0}));
+    assert(it == end);
+  }
+
+  // Test implicit syntax
+  {
+    using ChunkByView = std::ranges::chunk_by_view<Range, Pred>;
+    static_assert(!ImplicitConstructibleFrom<ChunkByView, Range, Pred>);
+    static_assert(!ImplicitConstructibleFrom<ChunkByView, const Range&, const Pred&>);
+  }
+
+  // Make sure we move the view
+  {
+    bool moved = false, copied = false;
+    TrackingRange range(&moved, &copied);
+    Pred pred;
+    [[maybe_unused]] std::ranges::chunk_by_view<TrackingRange, Pred> view(std::move(range), pred);
+    assert(moved);
+    assert(!copied);
+  }
+
+  // Make sure we move the predicate
+  {
+    bool moved = false, copied = false;
+    Range range(buff, buff + 9);
+    TrackingPred pred(&moved, &copied);
+    [[maybe_unused]] std::ranges::chunk_by_view<Range, TrackingPred> view(range, std::move(pred));
+    assert(moved);
+    assert(!copied);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/end.pass.cpp
new file mode 100644
index 000000000000000..61ea0c747195347
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/end.pass.cpp
@@ -0,0 +1,121 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// constexpr auto end();
+
+#include <ranges>
+
+#include <cassert>
+#include <concepts>
+#include <functional>
+
+#include "test_iterators.h"
+
+struct NonCommonRange : std::ranges::view_base {
+  using Iterator = forward_iterator<int*>;
+  using Sentinel = sentinel_wrapper<Iterator>;
+  constexpr explicit NonCommonRange(int* b, int* e) : begin_(b), end_(e) {}
+  constexpr Iterator begin() const { return Iterator(begin_); }
+  constexpr Sentinel end() const { return Sentinel(Iterator(end_)); }
+
+private:
+  int* begin_;
+  int* end_;
+};
+
+static_assert(std::ranges::forward_range<NonCommonRange>);
+static_assert(!std::ranges::common_range<NonCommonRange>);
+
+struct CommonRange : std::ranges::view_base {
+  using Iterator = bidirectional_iterator<int*>;
+  constexpr explicit CommonRange(int* b, int* e) : begin_(b), end_(e) {}
+  constexpr Iterator begin() const { return Iterator(begin_); }
+  constexpr Iterator end() const { return Iterator(end_); }
+
+private:
+  int* begin_;
+  int* end_;
+};
+
+static_assert(std::ranges::bidirectional_range<CommonRange>);
+static_assert(std::ranges::common_range<CommonRange>);
+
+constexpr bool test() {
+  int buff[] = {1, 0, 3, 1, 2, 3, 4, 5};
+
+  // Check the return type of `end()`
+  {
+    CommonRange range(buff, buff + 1);
+    auto pred = [](int, int) { return true; };
+    std::ranges::chunk_by_view view(range, pred);
+    using ChunkByView = decltype(view);
+    static_assert(std::ranges::common_range<ChunkByView>);
+    ASSERT_SAME_TYPE(std::ranges::sentinel_t<ChunkByView>, decltype(view.end()));
+  }
+
+  // end() on an empty range
+  {
+    CommonRange range(buff, buff);
+    auto pred = [](int x, int y) { return x <= y; };
+    std::ranges::chunk_by_view view(range, pred);
+    auto end = view.end();
+    assert(end == std::default_sentinel);
+  }
+
+  // end() on a 1-element range
+  {
+    CommonRange range(buff, buff + 1);
+    auto pred = [](int& x, int& y) { return x <= y; };
+    std::ranges::chunk_by_view view(range, pred);
+    auto end = view.end();
+    assert(base((*--end).begin()) == buff);
+    assert(base((*end).end()) == buff + 1);
+  }
+
+  // end() on a 2-element range
+  {
+    CommonRange range(buff, buff + 2);
+    auto pred = [](int const& x, int const& y) { return x <= y; };
+    std::ranges::chunk_by_view view(range, pred);
+    auto end = view.end();
+    assert(base((*--end).begin()) == buff + 1);
+    assert(base((*--end).end()) == buff + 1);
+  }
+
+  // end() on a 8-element range
+  {
+    CommonRange range(buff, buff + 8);
+    auto pred = [](const int x, const int y) { return x < y; };
+    std::ranges::chunk_by_view view(range, pred);
+    auto end = view.end();
+    assert(base((*--end).end()) == buff + 8);
+    assert(base((*--end).end()) == buff + 3);
+  }
+
+  // end() on a non-common range
+  {
+    NonCommonRange range(buff, buff + 1);
+    std::ranges::chunk_by_view view(range, std::ranges::less_equal{});
+    auto end = view.end();
+    ASSERT_SAME_TYPE(std::default_sentinel_t, std::ranges::sentinel_t<decltype(view)>);
+    ASSERT_SAME_TYPE(std::default_sentinel_t, decltype(end));
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/pred.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/pred.pass.cpp
new file mode 100644
index 000000000000000..6d697a14dc73591
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/pred.pass.cpp
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// constexpr Pred const& pred() const;
+
+#include <ranges>
+
+#include <cassert>
+#include <concepts>
+
+struct Range : std::ranges::view_base {
+  int* begin() const;
+  int* end() const;
+};
+
+struct Pred {
+  bool operator()(int, int) const;
+  int value;
+};
+
+constexpr bool test() {
+  {
+    Pred pred{42};
+    std::ranges::chunk_by_view<Range, Pred> const view(Range{}, pred);
+    std::same_as<Pred const&> decltype(auto) result = view.pred();
+    assert(result.value == 42);
+
+    // Make sure we're really holding a reference to something inside the view
+    assert(&result == &view.pred());
+  }
+
+  // Same, but calling on a non-const view
+  {
+    Pred pred{42};
+    std::ranges::chunk_by_view<Range, Pred> view(Range{}, pred);
+    std::same_as<Pred const&> decltype(auto) result = view.pred();
+    assert(result.value == 42);
+
+    // Make sure we're really holding a reference to something inside the view
+    assert(&result == &view.pred());
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/compare.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/compare.pass.cpp
new file mode 100644
index 000000000000000..38b6346e0061f79
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/compare.pass.cpp
@@ -0,0 +1,113 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// friend constexpr bool operator==(const iterator& x, const iterator& y);
+// friend constexpr bool operator==(const iterator& x, default_sentinel_t);
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <utility>
+
+#include "../types.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using Underlying      = View<Iterator, Sentinel>;
+  using ChunkByView     = std::ranges::chunk_by_view<Underlying, std::ranges::less_equal>;
+  using ChunkByIterator = std::ranges::iterator_t<ChunkByView>;
+
+  auto make_chunk_by_view = [](auto begin, auto end) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return ChunkByView(std::move(view), std::ranges::less_equal{});
+  };
+
+  // Test operator==
+  {
+    std::array array{0, 1, 2};
+    ChunkByView view  = make_chunk_by_view(array.begin(), array.end());
+    ChunkByIterator i = view.begin();
+    ChunkByIterator j = view.begin();
+
+    std::same_as<bool> decltype(auto) result = (i == j);
+    assert(result);
+    ++i;
+    assert(!(i == j));
+  }
+
+  // Test synthesized operator!=
+  {
+    std::array array{0, 1, 2};
+    ChunkByView view  = make_chunk_by_view(array.begin(), array.end());
+    ChunkByIterator i = view.begin();
+    ChunkByIterator j = view.begin();
+
+    std::same_as<bool> decltype(auto) result = (i != j);
+    assert(!result);
+    ++i;
+    assert(i != j);
+  }
+
+  // Test operator== with std::default_sentinel_t
+  {
+    std::array array{0, 1, 2};
+    ChunkByView view  = make_chunk_by_view(array.begin(), array.end());
+    ChunkByIterator i = view.begin();
+
+    std::same_as<bool> decltype(auto) result = (i == std::default_sentinel);
+    assert(!result);
+    ++i;
+    assert(i == std::default_sentinel);
+  }
+
+  // Test synthesized operator!= with std::default_sentinel_t
+  {
+    std::array array{0, 1, 2};
+    ChunkByView view  = make_chunk_by_view(array.begin(), array.end());
+    ChunkByIterator i = view.begin();
+
+    std::same_as<bool> decltype(auto) result = (i != std::default_sentinel);
+    assert(result);
+    ++i;
+    assert(!(i != std::default_sentinel));
+  }
+}
+
+struct TestWithPair {
+  template <class Iterator>
+  constexpr void operator()() const {
+    // Test with pair of iterators
+    test<Iterator, Iterator>();
+
+    // Test with iterator-sentinel pair
+    test<Iterator>();
+  }
+};
+
+constexpr bool tests() {
+  TestWithPair tester;
+  types::for_each(types::forward_iterator_list<int*>{}, tester);
+  types::for_each(types::forward_iterator_list<int const*>{}, tester);
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/ctor.default.pass.cpp
new file mode 100644
index 000000000000000..4493060c59bf761
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/ctor.default.pass.cpp
@@ -0,0 +1,50 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// std::ranges::chunk_by_view<V>::<iterator>() = default;
+
+#include <ranges>
+
+#include <cassert>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "../types.h"
+#include "test_iterators.h"
+
+template <class Iterator, bool IsNoexcept>
+constexpr void testDefaultConstructible() {
+  // Make sure the iterator is default constructible.
+  using ChunkByView     = std::ranges::chunk_by_view<View<Iterator>, std::ranges::less_equal>;
+  using ChunkByIterator = std::ranges::iterator_t<ChunkByView>;
+  ChunkByIterator i{};
+  ChunkByIterator j;
+  assert(i == j);
+  static_assert(noexcept(ChunkByIterator{}) == IsNoexcept);
+}
+
+constexpr bool tests() {
+  testDefaultConstructible<forward_iterator<int*>, /*IsNoexcept=*/false>();
+  testDefaultConstructible<bidirectional_iterator<int*>, /*IsNoexcept=*/false>();
+  testDefaultConstructible<random_access_iterator<int*>, /*IsNoexcept=*/false>();
+  testDefaultConstructible<contiguous_iterator<int*>, /*IsNoexcept=*/false>();
+  testDefaultConstructible<int*, /*IsNoexcept=*/true>();
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/decrement.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/decrement.pass.cpp
new file mode 100644
index 000000000000000..167f3f753bfae73
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/decrement.pass.cpp
@@ -0,0 +1,221 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// constexpr iterator& operator--();
+// constexpr iterator operator--(int);
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <span>
+#include <type_traits>
+#include <utility>
+
+#include "../types.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <class T>
+concept HasPreDecrement = requires(T t) {
+  { --t };
+};
+
+template <class T>
+concept HasPostDecrement = requires(T t) {
+  { t-- };
+};
+
+struct TrackingPred : TrackInitialization {
+  using TrackInitialization::TrackInitialization;
+  constexpr bool operator()(int x, int y) const { return x <= y; }
+};
+
+template <class Iterator, IsConst Constant, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using Underlying      = View<Iterator, Sentinel>;
+  using ChunkByView     = std::ranges::chunk_by_view<Underlying, std::ranges::less_equal>;
+  using ChunkByIterator = std::ranges::iterator_t<ChunkByView>;
+
+  static_assert(HasPostDecrement<ChunkByIterator>);
+  static_assert(HasPreDecrement<ChunkByIterator>);
+
+  auto make_chunk_by_view = [](auto begin, auto end) {
+    View view{Iterator{begin}, Sentinel{Iterator{end}}};
+    return ChunkByView{std::move(view), std::ranges::less_equal{}};
+  };
+
+  // Test with a single chunk
+  {
+    std::array array{0, 1, 2, 3, 4};
+    ChunkByView view   = make_chunk_by_view(array.begin(), array.end());
+    ChunkByIterator it = std::ranges::next(view.begin(), view.end());
+
+    std::same_as<ChunkByIterator&> decltype(auto) result = --it;
+    assert(&result == &it);
+    assert(base((*result).begin()) == array.begin());
+  }
+
+  // Test with two chunks
+  {
+    std::array array{0, 1, 2, 0, 1, 2};
+    ChunkByView view   = make_chunk_by_view(array.begin(), array.end());
+    ChunkByIterator it = std::ranges::next(view.begin(), view.end());
+
+    std::same_as<ChunkByIterator&> decltype(auto) result = --it;
+    assert(&result == &it);
+    assert(base((*result).begin()) == array.begin() + 3);
+
+    --it;
+    assert(base((*result).begin()) == array.begin());
+  }
+
+  // Test going forward and then backward on the same iterator
+  {
+    std::array array{7, 8, 9, 4, 5, 6, 1, 2, 3, 0};
+    ChunkByView view   = make_chunk_by_view(array.begin(), array.end());
+    ChunkByIterator it = view.begin();
+
+    ++it;
+    --it;
+    assert(base((*it).begin()) == array.begin());
+    assert(base((*it).end()) == array.begin() + 3);
+
+    ++it;
+    ++it;
+    --it;
+    assert(base((*it).begin()) == array.begin() + 3);
+    assert(base((*it).end()) == array.begin() + 6);
+
+    ++it;
+    ++it;
+    --it;
+    assert(base((*it).begin()) == array.begin() + 6);
+    assert(base((*it).end()) == array.begin() + 9);
+
+    ++it;
+    ++it;
+    --it;
+    assert(base((*it).begin()) == array.begin() + 9);
+  }
+
+  // Decrement an iterator multiple times
+  if constexpr (std::ranges::common_range<Underlying>) {
+    std::array array{1, 2, 1, 2, 1};
+    ChunkByView view = make_chunk_by_view(array.begin(), array.end());
+
+    ChunkByIterator it = view.end();
+    --it;
+    --it;
+    --it;
+    assert(base((*it).begin()) == array.begin());
+  }
+
+  // Test with a predicate that takes by non-const reference
+  if constexpr (!std::to_underlying(Constant)) {
+    std::array array{1, 2, 3, -3, -2, -1};
+    View v{Iterator{array.begin()}, Sentinel{Iterator{array.end()}}};
+    auto view = std::views::chunk_by(std::move(v), [](int& x, int& y) { return x <= y; });
+
+    auto it = std::ranges::next(view.begin());
+    assert(base((*it).begin()) == array.begin() + 3);
+    --it;
+    assert(base((*it).begin()) == array.begin());
+  }
+
+  // Test with a predicate that is invocable but not callable (i.e. cannot be called like regular function 'f()')
+  {
+    std::array array = {1, 2, 3, -3, -2, -1};
+    auto v = View{Iterator{array.begin()}, Sentinel{Iterator{array.end()}}}
+           | std::views::transform([](int x) { return IntWrapper{x}; });
+    auto view = std::views::chunk_by(std::move(v), &IntWrapper::lessEqual);
+
+    auto it = std::ranges::next(view.begin());
+    assert(base((*it).begin().base()) == array.begin() + 3);
+    --it;
+    assert(base((*it).begin().base()) == array.begin());
+  }
+
+  // Make sure we do not make a copy of the predicate when we decrement
+  if constexpr (std::ranges::common_range<Underlying>) {
+    bool moved = false, copied = false;
+    std::array array{1, 2, 1, 3};
+    View v{Iterator(array.begin()), Sentinel(Iterator(array.end()))};
+    auto view = std::views::chunk_by(std::move(v), TrackingPred(&moved, &copied));
+    assert(std::exchange(moved, false));
+    auto it = view.end();
+    --it;
+    it--;
+    assert(!moved);
+    assert(!copied);
+  }
+
+  // Check post-decrement
+  {
+    std::array array{0, 1, 2, -3, -2, -1, -6, -5, -4};
+    ChunkByView view   = make_chunk_by_view(array.begin(), array.end());
+    ChunkByIterator it = std::ranges::next(view.begin(), view.end());
+
+    std::same_as<ChunkByIterator> decltype(auto) result = it--;
+    assert(result != it);
+    assert(result == std::default_sentinel);
+    assert(base((*it).begin()) == array.begin() + 6);
+
+    result = it--;
+    assert(base((*it).begin()) == array.begin() + 3);
+    assert(base((*result).begin()) == array.begin() + 6);
+
+    result = it--;
+    assert(base((*it).begin()) == array.begin());
+    assert(base((*result).begin()) == array.begin() + 3);
+  }
+}
+
+template <class Iterator, IsConst Constant>
+constexpr void test_with_pair() {
+  // Test with pair of iterators
+  test<Iterator, Constant>();
+
+  // Test with iterator-sentinel pair
+  test<Iterator, Constant, Iterator>();
+}
+
+constexpr bool tests() {
+  test_with_pair<bidirectional_iterator<int*>, IsConst::no>();
+  test_with_pair<random_access_iterator<int*>, IsConst::no>();
+  test_with_pair<contiguous_iterator<int*>, IsConst::no>();
+  test_with_pair<int*, IsConst::no>();
+
+  test_with_pair<bidirectional_iterator<int const*>, IsConst::yes>();
+  test_with_pair<random_access_iterator<int const*>, IsConst::yes>();
+  test_with_pair<contiguous_iterator<int const*>, IsConst::yes>();
+  test_with_pair<int const*, IsConst::yes>();
+
+  // Make sure `operator--` isn't provided for non bidirectional ranges
+  {
+    using ForwardView = View<forward_iterator<int*>, sentinel_wrapper<forward_iterator<int*>>>;
+    using ChunkByView = std::ranges::chunk_by_view<ForwardView, std::ranges::less_equal>;
+    static_assert(!HasPreDecrement<std::ranges::iterator_t<ChunkByView>>);
+    static_assert(!HasPostDecrement<std::ranges::iterator_t<ChunkByView>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/deref.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/deref.pass.cpp
new file mode 100644
index 000000000000000..3f8c073e7b3b068
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/deref.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, c++20
+
+// <ranges>
+
+// constexpr value_type operator*() const;
+
+#include <ranges>
+
+#include <algorithm>
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <utility>
+
+#include "../types.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+template <class Iter, class Sent = sentinel_wrapper<Iter>>
+constexpr void test() {
+  using Underlying      = View<Iter, Sent>;
+  using ChunkByView     = std::ranges::chunk_by_view<Underlying, std::ranges::less_equal>;
+  using ChunkByIterator = std::ranges::iterator_t<ChunkByView>;
+
+  std::array array{0, 1, 2, 3, -1, 0, 1, 2, -2, 3, 4, 5};
+  std::array expected{std::array{0, 1, 2, 3}, std::array{-1, 0, 1, 2}, std::array{-2, 3, 4, 5}};
+  Underlying underlying{Iter{array.begin()}, Sent{Iter{array.end()}}};
+  ChunkByView view{underlying, std::ranges::less_equal{}};
+
+  size_t idx = 0;
+  for (std::same_as<ChunkByIterator> auto iter = view.begin(); iter != view.end(); ++idx, ++iter) {
+    std::same_as<typename ChunkByIterator::value_type> auto chunk = *iter;
+    assert(std::ranges::equal(chunk, expected[idx]));
+  }
+}
+
+constexpr bool tests() {
+  // Check iterator-sentinel pair
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+
+  // Check iterator pair
+  test<forward_iterator<int*>, forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>, bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>, random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>, contiguous_iterator<int*>>();
+  test<int*, int*>();
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/increment.pass.cpp
new file mode 100644
index 000000000000000..454e9e7503a5b3a
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/increment.pass.cpp
@@ -0,0 +1,179 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// constexpr iterator& operator++();
+// constexpr iterator operator++(int);
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <functional>
+#include <span>
+#include <type_traits>
+#include <utility>
+
+#include "../types.h"
+#include "test_iterators.h"
+#include "test_macros.h"
+
+struct TrackingPred : TrackInitialization {
+  using TrackInitialization::TrackInitialization;
+  constexpr bool operator()(int x, int y) const { return x <= y; }
+};
+
+template <class Iterator, IsConst Constant>
+constexpr void test() {
+  using Sentinel        = sentinel_wrapper<Iterator>;
+  using Underlying      = View<Iterator, Sentinel>;
+  using ChunkByView     = std::ranges::chunk_by_view<Underlying, std::ranges::less_equal>;
+  using ChunkByIterator = std::ranges::iterator_t<ChunkByView>;
+
+  auto make_chunk_by_view = [](auto begin, auto end) {
+    View view{Iterator{begin}, Sentinel{Iterator{end}}};
+    return ChunkByView{std::move(view), std::ranges::less_equal{}};
+  };
+
+  // Increment the iterator when it won't find another satisfied value after begin()
+  {
+    std::array array{0, 1, 2, 3, 4};
+    ChunkByView view   = make_chunk_by_view(array.begin(), array.end());
+    ChunkByIterator it = view.begin();
+
+    std::same_as<ChunkByIterator&> decltype(auto) result = ++it;
+    assert(&result == &it);
+    assert(result == view.end());
+    assert(result == std::default_sentinel);
+  }
+
+  // Increment the iterator and it finds another value after begin()
+  {
+    std::array array{1, 2, 3, -1, -2, -3};
+    int const* second_chunk = array.begin() + 3;
+    ChunkByView view        = make_chunk_by_view(array.begin(), array.end());
+
+    ChunkByIterator it = view.begin();
+    ++it;
+    assert(base((*it).begin()) == second_chunk);
+  }
+
+  // Increment advances all the way to the end of the range
+  {
+    std::array array{1, 2, 3, 4, 1};
+    ChunkByView view = make_chunk_by_view(array.begin(), array.end());
+
+    ChunkByIterator it = view.begin();
+    ++it;
+    assert(base((*it).begin()) == array.begin() + 4);
+  }
+
+  // Increment an iterator multiple times
+  {
+    std::array array{0, 1, 0, 2, 0, 3, 0, 4};
+    ChunkByView view = make_chunk_by_view(array.begin(), array.end());
+
+    ChunkByIterator it = view.begin();
+    assert(base((*it).begin()) == array.begin());
+    ++it;
+    assert(base((*it).begin()) == array.begin() + 2);
+    ++it;
+    assert(base((*it).begin()) == array.begin() + 4);
+    ++it;
+    assert(base((*it).begin()) == array.begin() + 6);
+    ++it;
+    assert(it == std::default_sentinel);
+  }
+
+  // Test with a predicate that takes by non-const reference
+  if constexpr (!std::to_underlying(Constant)) {
+    std::array array{1, 2, 3, -3, -2, -1};
+    View v{Iterator{array.begin()}, Sentinel{Iterator{array.end()}}};
+    auto view = std::views::chunk_by(std::move(v), [](int& x, int& y) { return x <= y; });
+
+    auto it = view.begin();
+    assert(base((*it).begin()) == array.begin());
+    ++it;
+    assert(base((*it).begin()) == array.begin() + 3);
+  }
+
+  // Test with a predicate that is invocable but not callable (i.e. cannot be called like regular function 'f()')
+  {
+    std::array array = {1, 2, 3, -3, -2, -1};
+    auto v = View{Iterator{array.begin()}, Sentinel{Iterator{array.end()}}}
+           | std::views::transform([](int x) { return IntWrapper{x}; });
+    auto view = std::views::chunk_by(std::move(v), &IntWrapper::lessEqual);
+
+    auto it = view.begin();
+    assert(base((*it).begin().base()) == array.begin());
+    ++it;
+    assert(base((*it).begin().base()) == array.begin() + 3);
+  }
+
+  // Make sure we do not make a copy of the predicate when we increment
+  // (we should be passing it to ranges::adjacent_find using std::ref)
+  {
+    bool moved = false, copied = false;
+    std::array array{1, 2, 1, 3};
+    View v{Iterator(array.begin()), Sentinel(Iterator(array.end()))};
+    auto view = std::views::chunk_by(std::move(v), TrackingPred(&moved, &copied));
+    assert(std::exchange(moved, false));
+    auto it = view.begin();
+    ++it;
+    it++;
+    assert(!moved);
+    assert(!copied);
+  }
+
+  // Check post-increment
+  {
+    std::array array{0, 1, 2, -3, -2, -1, -6, -5, -4};
+    ChunkByView view   = make_chunk_by_view(array.begin(), array.end());
+    ChunkByIterator it = view.begin();
+
+    std::same_as<ChunkByIterator> decltype(auto) result = it++;
+    assert(result != it);
+    assert(base((*result).begin()) == array.begin());
+    assert(base((*it).begin()) == array.begin() + 3);
+
+    result = it++;
+    assert(base((*result).begin()) == array.begin() + 3);
+    assert(base((*it).begin()) == array.begin() + 6);
+
+    result = it++;
+    assert(base((*result).begin()) == array.begin() + 6);
+    assert(it == std::default_sentinel);
+  }
+}
+
+constexpr bool tests() {
+  test<forward_iterator<int*>, IsConst::no>();
+  test<bidirectional_iterator<int*>, IsConst::no>();
+  test<random_access_iterator<int*>, IsConst::no>();
+  test<contiguous_iterator<int*>, IsConst::no>();
+  test<int*, IsConst::no>();
+
+  test<forward_iterator<int const*>, IsConst::yes>();
+  test<bidirectional_iterator<int const*>, IsConst::yes>();
+  test<random_access_iterator<int const*>, IsConst::yes>();
+  test<contiguous_iterator<int const*>, IsConst::yes>();
+  test<int const*, IsConst::yes>();
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+
+  return 0;
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/types.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/types.compile.pass.cpp
new file mode 100644
index 000000000000000..2093936ccf79909
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/range.chunk.by.iter/types.compile.pass.cpp
@@ -0,0 +1,71 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+
+// <ranges>
+
+// std::ranges::chunk_by_view::<iterator>::value_type
+// std::ranges::chunk_by_view::<iterator>::
diff erence_type
+// std::ranges::chunk_by_view::<iterator>::iterator_category
+// std::ranges::chunk_by_view::<iterator>::iterator_concept
+
+#include <ranges>
+
+#include <functional>
+#include <type_traits>
+
+#include "../types.h"
+#include "test_iterators.h"
+
+struct TestValueTypeAndDifferenceType {
+  template <class Iter>
+  constexpr void operator()() const {
+    using Underlying      = View<Iter>;
+    using ChunkByView     = std::ranges::chunk_by_view<Underlying, std::ranges::less_equal>;
+    using ChunkByIterator = std::ranges::iterator_t<ChunkByView>;
+    static_assert(std::same_as<typename ChunkByIterator::value_type, std::ranges::range_value_t<ChunkByView>>);
+    static_assert(std::same_as<typename ChunkByIterator::value_type, std::ranges::subrange<Iter>>);
+    static_assert(
+        std::same_as<typename ChunkByIterator::
diff erence_type, std::ranges::range_
diff erence_t<ChunkByView>>);
+    static_assert(std::same_as<typename ChunkByIterator::
diff erence_type, std::ranges::range_
diff erence_t<Underlying>>);
+  }
+};
+
+template <class Iter>
+using ChunkByIteratorFor = std::ranges::iterator_t<std::ranges::chunk_by_view<View<Iter>, std::ranges::less_equal>>;
+
+constexpr void test() {
+  // Check that value_type is range_value_t and 
diff erence_type is range_
diff erence_t
+  types::for_each(types::forward_iterator_list<int*>{}, TestValueTypeAndDifferenceType{});
+
+  // Check iterator_concept for various categories of ranges
+  {
+    static_assert(
+        std::same_as<ChunkByIteratorFor<forward_iterator<int*>>::iterator_concept, std::forward_iterator_tag>);
+    static_assert(std::same_as<ChunkByIteratorFor<bidirectional_iterator<int*>>::iterator_concept,
+                               std::bidirectional_iterator_tag>);
+    static_assert(std::same_as<ChunkByIteratorFor<random_access_iterator<int*>>::iterator_concept,
+                               std::bidirectional_iterator_tag>);
+    static_assert(
+        std::same_as<ChunkByIteratorFor<contiguous_iterator<int*>>::iterator_concept, std::bidirectional_iterator_tag>);
+    static_assert(std::same_as<ChunkByIteratorFor<int*>::iterator_concept, std::bidirectional_iterator_tag>);
+  }
+
+  // Check iterator_category for various categories of ranges
+  {
+    static_assert(std::same_as<ChunkByIteratorFor<forward_iterator<int*>>::iterator_category, std::input_iterator_tag>);
+    static_assert(
+        std::same_as<ChunkByIteratorFor<bidirectional_iterator<int*>>::iterator_category, std::input_iterator_tag>);
+    static_assert(
+        std::same_as<ChunkByIteratorFor<random_access_iterator<int*>>::iterator_category, std::input_iterator_tag>);
+    static_assert(
+        std::same_as<ChunkByIteratorFor<contiguous_iterator<int*>>::iterator_category, std::input_iterator_tag>);
+    static_assert(std::same_as<ChunkByIteratorFor<int*>::iterator_category, std::input_iterator_tag>);
+  }
+}

diff  --git a/libcxx/test/std/ranges/range.adaptors/range.chunk.by/types.h b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/types.h
new file mode 100644
index 000000000000000..ecbf0c73acf4072
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk.by/types.h
@@ -0,0 +1,54 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_CHUNK_BY_TYPES_H
+#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_CHUNK_BY_TYPES_H
+
+#include <ranges>
+#include <utility>
+
+#include "test_iterators.h"
+
+struct TrackInitialization {
+  constexpr explicit TrackInitialization(bool* moved, bool* copied) : moved_(moved), copied_(copied) {}
+  constexpr TrackInitialization(TrackInitialization const& other) : moved_(other.moved_), copied_(other.copied_) {
+    *copied_ = true;
+  }
+  constexpr TrackInitialization(TrackInitialization&& other) : moved_(other.moved_), copied_(other.copied_) {
+    *moved_ = true;
+  }
+  TrackInitialization& operator=(TrackInitialization const&) = default;
+  TrackInitialization& operator=(TrackInitialization&&)      = default;
+  bool* moved_;
+  bool* copied_;
+};
+
+enum class IsConst : bool { no, yes };
+
+template <std::forward_iterator Iter, std::sentinel_for<Iter> Sent = sentinel_wrapper<Iter>>
+struct View : std::ranges::view_base {
+  constexpr explicit View(Iter b, Sent e) : begin_(b), end_(e) {}
+  constexpr Iter begin() { return begin_; }
+  constexpr Sent end() { return end_; }
+
+private:
+  Iter begin_;
+  Sent end_;
+};
+
+template <class I, class S>
+View(I b, S e) -> View<I, S>;
+
+struct IntWrapper {
+  constexpr IntWrapper(int v) : value_(v) {}
+
+  int value_;
+  constexpr bool lessEqual(IntWrapper other) const { return value_ <= other.value_; }
+};
+
+#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_CHUNK_BY_TYPES_H

diff  --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index c2251c8d817c1ce..ac342aff0beb701 100755
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -801,7 +801,6 @@ def add_version_header(tc):
             "name": "__cpp_lib_ranges_chunk_by",
             "values": {"c++23": 202202},
             "headers": ["ranges"],
-            "unimplemented": True,
         },
         {
             "name": "__cpp_lib_ranges_iota",


        


More information about the libcxx-commits mailing list