[libcxx-commits] [libcxx] [libc++][ranges] implement `ranges::chunk_view` for `forward_range` (PR #85741)

Xiaoyang Liu via libcxx-commits libcxx-commits at lists.llvm.org
Tue Mar 19 12:44:29 PDT 2024


https://github.com/xiaoyang-sde updated https://github.com/llvm/llvm-project/pull/85741

>From 7a38a42d7954be707562a7aa4a8915ea6b6cd980 Mon Sep 17 00:00:00 2001
From: Xiaoyang Liu <siujoeng.lau at gmail.com>
Date: Mon, 18 Mar 2024 23:29:02 -0700
Subject: [PATCH 1/3] [libc++] [ranges] implement 'ranges::chunk_view' for
 'forward_range'

---
 libcxx/include/CMakeLists.txt        |   1 +
 libcxx/include/__ranges/chunk_view.h | 356 +++++++++++++++++++++++++++
 libcxx/include/ranges                |   1 +
 3 files changed, 358 insertions(+)
 create mode 100644 libcxx/include/__ranges/chunk_view.h

diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index 6ed8d21d98a15a..88a91ce67bda7a 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -625,6 +625,7 @@ set(files
   __ranges/all.h
   __ranges/as_rvalue_view.h
   __ranges/chunk_by_view.h
+  __ranges/chunk_view.h
   __ranges/common_view.h
   __ranges/concepts.h
   __ranges/container_compatible_range.h
diff --git a/libcxx/include/__ranges/chunk_view.h b/libcxx/include/__ranges/chunk_view.h
new file mode 100644
index 00000000000000..e3ffdd376207e1
--- /dev/null
+++ b/libcxx/include/__ranges/chunk_view.h
@@ -0,0 +1,356 @@
+// -*- 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_VIEW_H
+#define _LIBCPP___RANGES_CHUNK_VIEW_H
+
+#include <__concepts/constructible.h>
+#include <__config>
+#include <__functional/bind_back.h>
+#include <__iterator/advance.h>
+#include <__iterator/concepts.h>
+#include <__iterator/default_sentinel.h>
+#include <__iterator/distance.h>
+#include <__ranges/access.h>
+#include <__ranges/all.h>
+#include <__ranges/concepts.h>
+#include <__ranges/subrange.h>
+#include <__ranges/take_view.h>
+#include <__ranges/view_interface.h>
+#include <__type_traits/make_unsigned.h>
+#include <__type_traits/maybe_const.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 <class _Integer>
+constexpr _Integer __div_ceil(_Integer __num, _Integer __denom) {
+  _Integer __r = __num / __denom;
+  if (__num % __denom) {
+    ++__r;
+  }
+  return __r;
+}
+
+template <view _View>
+  requires input_range<_View>
+class chunk_view : public view_interface<chunk_view<_View>> {};
+
+template <view _View>
+  requires forward_range<_View>
+class chunk_view<_View> : public view_interface<chunk_view<_View>> {
+private:
+  _LIBCPP_NO_UNIQUE_ADDRESS _View __base_;
+  range_difference_t<_View> __n_;
+
+  template <bool>
+  class __iterator;
+
+public:
+  _LIBCPP_HIDE_FROM_ABI constexpr explicit chunk_view(_View __base, range_difference_t<_View> __n)
+      : __base_(std::move(__base)), __n_(__n) {
+    _LIBCPP_ASSERT_UNCATEGORIZED(__n > 0, "__n must be greater than 0");
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr _View base() const&
+    requires copy_constructible<_View>
+  {
+    return __base_;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr _View base() && { return std::move(__base_); }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto begin()
+    requires(!__simple_view<_View>)
+  {
+    return __iterator<false>(this, ranges::begin(__base_));
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto begin() const
+    requires forward_range<const _View>
+  {
+    return __iterator<true>(this, ranges::begin(__base_));
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto end()
+    requires(!__simple_view<_View>)
+  {
+    if constexpr (common_range<_View> && sized_range<_View>) {
+      auto __missing = (__n_ - ranges::distance(__base_) % __n_) % __n_;
+      return __iterator<false>(this, ranges::end(__base_), __missing);
+    } else if constexpr (common_range<_View> && !bidirectional_range<_View>) {
+      return __iterator<false>(this, ranges::end(__base_));
+    } else {
+      return default_sentinel;
+    }
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto end() const
+    requires forward_range<_View>
+  {
+    if constexpr (common_range<const _View> && sized_range<const _View>) {
+      auto __missing = (__n_ - ranges::distance(__base_) % __n_) % __n_;
+      return __iterator<true>(this, ranges::end(__base_), __missing);
+    } else if constexpr (common_range<const _View> && !bidirectional_range<const _View>) {
+      return __iterator<true>(this, ranges::end(__base_));
+    } else {
+      return default_sentinel;
+    }
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto size()
+    requires sized_range<_View>
+  {
+    return std::__to_unsigned_like(__div_ceil(ranges::distance(__base_), __n_));
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto size() const
+    requires sized_range<const _View>
+  {
+    return std::__to_unsigned_like(__div_ceil(ranges::distance(__base_), __n_));
+  }
+};
+
+template <class _View>
+chunk_view(_View&&, range_difference_t<_View>) -> chunk_view<views::all_t<_View>>;
+
+template <class _View>
+inline constexpr bool enable_borrowed_range<chunk_view<_View>> = enable_borrowed_range<_View> && forward_range<_View>;
+
+template <view _View>
+  requires forward_range<_View>
+template <bool _Const>
+class chunk_view<_View>::__iterator {
+private:
+  friend chunk_view;
+
+  using _Parent = __maybe_const<_Const, chunk_view>;
+  using _Base   = __maybe_const<_Const, _View>;
+
+  iterator_t<_Base> __current_         = iterator_t<_Base>();
+  sentinel_t<_Base> __end_             = sentinel_t<_Base>();
+  range_difference_t<_Base> __n_       = 0;
+  range_difference_t<_Base> __missing_ = 0;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator(
+      _Parent* __parent, iterator_t<_Base> __current, range_difference_t<_Base> __missing = 0)
+      : __current_(std::move(__current)),
+        __end_(ranges::end(__parent->__base_), __n_(__parent->__n_)),
+        __missing_(__missing) {}
+
+public:
+  using iterator_category = input_iterator_tag;
+  using iterator_concept =
+      conditional_t<random_access_range<_Base>,
+                    random_access_iterator_tag,
+                    conditional_t<bidirectional_range<_Base>, bidirectional_iterator_tag, forward_iterator_tag>>;
+  using value_type      = decltype(views::take(subrange(__current_, __end_), __n_));
+  using difference_type = range_difference_t<_Base>;
+
+  _LIBCPP_HIDE_FROM_ABI __iterator() = default;
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator(__iterator<!_Const> __i)
+    requires _Const && convertible_to<iterator_t<_View>, iterator_t<_Base>> &&
+                 convertible_to<sentinel_t<_View>, sentinel_t<_Base>>
+      : __current_(std::move(__i.__current_)),
+        __end_(std::move(__i.__end_)),
+        __n_(__i.__n_),
+        __missing_(__i.__missing_) {}
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr iterator_t<_Base> base() const { return __current_; }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr value_type operator*() const {
+    _LIBCPP_ASSERT_PEDANTIC(__current_ != __end_, "Dereferencing past-the-end chunk_view iterator");
+    return views::take(subrange(__current_, __end_), __n_);
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator++() {
+    _LIBCPP_ASSERT_PEDANTIC(__current_ != __end_, "Incrementing past-the-end chunk_view iterator");
+    __missing_ = ranges::advance(__current_, __n_, __end_);
+    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<_Base>
+  {
+    ranges::advance(__current_, __missing_ - __n_);
+    __missing_ = 0;
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator operator--(int)
+    requires bidirectional_range<_Base>
+  {
+    auto __tmp = *this;
+    --*this;
+    return __tmp;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator+=(const difference_type __offset)
+    requires random_access_range<_Base>
+  {
+    if (__offset > 0) {
+      ranges::advance(__current_, __n_ * (__offset - 1));
+      __missing_ = ranges::advance(__current_, __n_, __end_);
+    } else if (__offset < 0) {
+      ranges::advance(__current_, __n_ * __offset + __missing_);
+      __missing_ = 0;
+    }
+    return *this;
+  }
+
+  _LIBCPP_HIDE_FROM_ABI constexpr __iterator& operator-=(const difference_type __offset)
+    requires random_access_range<_Base>
+  {
+    return *this += -__offset;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr value_type operator[](const difference_type __offset) const
+    requires random_access_range<_Base>
+  {
+    return *(*this + __offset);
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator==(const __iterator& __x, const __iterator& __y) {
+    return __x.__current_ == __y.__current_;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator==(const __iterator& __x, default_sentinel_t) {
+    return __x.__current_ == __x.__end_;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator==(const __iterator& __x, const __iterator& __y)
+    requires random_access_range<_Base>
+  {
+    return __x.__current_ < __y.__current_;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator>(const __iterator& __x, const __iterator& __y)
+    requires random_access_range<_Base>
+  {
+    return __y < __x;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator<=(const __iterator& __x, const __iterator& __y)
+    requires random_access_range<_Base>
+  {
+    return !(__y < __x);
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr bool
+  operator>=(const __iterator& __x, const __iterator& __y)
+    requires random_access_range<_Base>
+  {
+    return !(__x < __y);
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr auto
+  operator<=>(const __iterator& __x, const __iterator& __y)
+    requires random_access_range<_Base> && three_way_comparable<iterator_t<_Base>>
+  {
+    return __x.__current_ <=> __y.__current_;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator
+  operator+(const __iterator& __x, const difference_type __y)
+    requires random_access_range<_Base>
+  {
+    return __iterator{__x} += __y;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator
+  operator+(const difference_type __x, const __iterator& __y)
+    requires random_access_range<_Base>
+  {
+    return __y + __x;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr __iterator
+  operator-(const __iterator& __x, difference_type __y)
+    requires random_access_range<_Base>
+  {
+    return __iterator{__x} -= __y;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr difference_type
+  operator-(const __iterator& __x, const __iterator& __y)
+    requires sized_sentinel_for<iterator_t<_Base>, iterator_t<_Base>>
+  {
+    return (__x.__current_ - __y.__current_ + __x.__missing_ - __y.__missing_) / __x.n_;
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr difference_type
+  operator-(const default_sentinel_t, const __iterator& __x)
+    requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
+  {
+    return __div_ceil(__x.__end_ - __x.__current_, __x.__n_);
+  }
+
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI friend constexpr difference_type
+  operator-(const __iterator& __x, const default_sentinel_t __y)
+    requires sized_sentinel_for<sentinel_t<_Base>, iterator_t<_Base>>
+  {
+    return -(__y - __x);
+  }
+};
+
+namespace views {
+namespace __chunk {
+struct __fn {
+  template <class _Range, convertible_to<range_difference_t<_Range>> _Np>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto
+  operator()(_Range&& __range,
+             _Np&& __n) const noexcept(noexcept(chunk_view(std::forward<_Range>(__range), std::forward<_Np>(__n))))
+      -> decltype(chunk_view(std::forward<_Range>(__range), std::forward<_Np>(__n))) {
+    return chunk_view(std::forward<_Range>(__range), std::forward<_Np>(__n));
+  }
+
+  template <class _Np>
+    requires constructible_from<decay_t<_Np>, _Np>
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto
+  operator()(_Np&& __n) const noexcept(is_nothrow_constructible_v<decay_t<_Np>, _Np>) {
+    return __range_adaptor_closure_t(std::__bind_back(*this, std::forward<_Np>(__n)));
+  }
+};
+} // namespace __chunk
+
+inline namespace __cpo {
+inline constexpr auto chunk = __chunk::__fn{};
+} // namespace __cpo
+} // namespace views
+
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER >= 23
+
+_LIBCPP_END_NAMESPACE_STD
+
+_LIBCPP_POP_MACROS
+
+#endif // _LIBCPP___RANGES_CHUNK_VIEW_H
diff --git a/libcxx/include/ranges b/libcxx/include/ranges
index 167d2137eaf454..1a63cef09dfb6b 100644
--- a/libcxx/include/ranges
+++ b/libcxx/include/ranges
@@ -380,6 +380,7 @@ namespace std {
 #include <__ranges/all.h>
 #include <__ranges/as_rvalue_view.h>
 #include <__ranges/chunk_by_view.h>
+#include <__ranges/chunk_view.h>
 #include <__ranges/common_view.h>
 #include <__ranges/concepts.h>
 #include <__ranges/counted.h>

>From 060cafd65ff6f9bce32ae3f5a26892ecb816ed88 Mon Sep 17 00:00:00 2001
From: Xiaoyang Liu <siujoeng.lau at gmail.com>
Date: Tue, 19 Mar 2024 10:52:06 -0700
Subject: [PATCH 2/3] [libc++][ranges] add missing headers to 'chunk_view.h'

---
 libcxx/include/__ranges/chunk_view.h | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/libcxx/include/__ranges/chunk_view.h b/libcxx/include/__ranges/chunk_view.h
index e3ffdd376207e1..817a41025bf203 100644
--- a/libcxx/include/__ranges/chunk_view.h
+++ b/libcxx/include/__ranges/chunk_view.h
@@ -10,15 +10,18 @@
 #define _LIBCPP___RANGES_CHUNK_VIEW_H
 
 #include <__concepts/constructible.h>
+#include <__concepts/convertible_to.h>
 #include <__config>
 #include <__functional/bind_back.h>
 #include <__iterator/advance.h>
 #include <__iterator/concepts.h>
 #include <__iterator/default_sentinel.h>
 #include <__iterator/distance.h>
+#include <__iterator/iterator_traits.h>
 #include <__ranges/access.h>
 #include <__ranges/all.h>
 #include <__ranges/concepts.h>
+#include <__ranges/enable_borrowed_range.h>
 #include <__ranges/subrange.h>
 #include <__ranges/take_view.h>
 #include <__ranges/view_interface.h>
@@ -324,17 +327,16 @@ namespace views {
 namespace __chunk {
 struct __fn {
   template <class _Range, convertible_to<range_difference_t<_Range>> _Np>
-  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto
-  operator()(_Range&& __range,
-             _Np&& __n) const noexcept(noexcept(chunk_view(std::forward<_Range>(__range), std::forward<_Np>(__n))))
-      -> decltype(chunk_view(std::forward<_Range>(__range), std::forward<_Np>(__n))) {
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Range&& __range, _Np&& __n) const
+      noexcept(noexcept(chunk_view(std::forward<_Range>(__range), std::forward<_Np>(__n))))
+          -> decltype(chunk_view(std::forward<_Range>(__range), std::forward<_Np>(__n))) {
     return chunk_view(std::forward<_Range>(__range), std::forward<_Np>(__n));
   }
 
   template <class _Np>
     requires constructible_from<decay_t<_Np>, _Np>
-  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto
-  operator()(_Np&& __n) const noexcept(is_nothrow_constructible_v<decay_t<_Np>, _Np>) {
+  _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr auto operator()(_Np&& __n) const
+      noexcept(is_nothrow_constructible_v<decay_t<_Np>, _Np>) {
     return __range_adaptor_closure_t(std::__bind_back(*this, std::forward<_Np>(__n)));
   }
 };

>From 5fed8c6215d2b31fd5666d92dd0f200e5a1cc4e5 Mon Sep 17 00:00:00 2001
From: Xiaoyang Liu <siujoeng.lau at gmail.com>
Date: Tue, 19 Mar 2024 12:44:14 -0700
Subject: [PATCH 3/3] [libc++][ranges] implement 'ranges::chunk_view' for
 'forward_range'

---
 libcxx/docs/Status/Cxx23Papers.csv            |   2 +-
 libcxx/docs/Status/RangesViews.csv            |   2 +-
 libcxx/include/__ranges/chunk_view.h          |   3 +-
 .../range.chunk/adaptor.nodiscard.verify.cpp  |  24 +++
 .../range.chunk/adaptor.pass.cpp              | 192 ++++++++++++++++++
 .../range.adaptors/range.chunk/base.pass.cpp  |  98 +++++++++
 .../range.adaptors/range.chunk/ctad.pass.cpp  |  62 ++++++
 7 files changed, 380 insertions(+), 3 deletions(-)
 create mode 100644 libcxx/test/libcxx/ranges/range.adaptors/range.chunk/adaptor.nodiscard.verify.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.chunk/adaptor.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.chunk/base.pass.cpp
 create mode 100644 libcxx/test/std/ranges/range.adaptors/range.chunk/ctad.pass.cpp

diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv
index 80547c5c1f3f57..cef23388994118 100644
--- a/libcxx/docs/Status/Cxx23Papers.csv
+++ b/libcxx/docs/Status/Cxx23Papers.csv
@@ -48,7 +48,7 @@
 "`P2387R3 <https://wg21.link/P2387R3>`__","LWG","Pipe support for user-defined range adaptors","February 2022","","","|ranges|"
 "`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","|In Progress|","","|ranges|"
-"`P2442R1 <https://wg21.link/P2442R1>`__","LWG","Windowing range adaptors: ``views::chunk`` and ``views::slide``","February 2022","","","|ranges|"
+"`P2442R1 <https://wg21.link/P2442R1>`__","LWG","Windowing range adaptors: ``views::chunk`` and ``views::slide``","February 2022",""|In progress|,"","|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","|Complete|","18.0"
diff --git a/libcxx/docs/Status/RangesViews.csv b/libcxx/docs/Status/RangesViews.csv
index f141656eb131a2..17ea6290560103 100644
--- a/libcxx/docs/Status/RangesViews.csv
+++ b/libcxx/docs/Status/RangesViews.csv
@@ -30,7 +30,7 @@ C++23,`adjacent <https://wg21.link/P2321R2>`_,Hui Xie,No patch yet,Not started
 C++23,`adjacent_transform <https://wg21.link/P2321R2>`_,Hui Xie,No patch yet,Not started
 C++23,`join_with <https://wg21.link/P2441R2>`_,Jakub Mazurkiewicz,`65536 <https://github.com/llvm/llvm-project/pull/65536>`_,In progress
 C++23,`slide <https://wg21.link/P2442R1>`_,Will Hawkins,`67146 <https://github.com/llvm/llvm-project/pull/67146>`_,In Progress
-C++23,`chunk <https://wg21.link/P2442R1>`_,Unassigned,No patch yet,Not started
+C++23,`chunk <https://wg21.link/P2442R1>`_,Xiaoyang Liu,`85741 <https://github.com/llvm/llvm-project/pull/85741>`_,In Progress
 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>`_,✅
diff --git a/libcxx/include/__ranges/chunk_view.h b/libcxx/include/__ranges/chunk_view.h
index 817a41025bf203..5ae27d6a4be5e8 100644
--- a/libcxx/include/__ranges/chunk_view.h
+++ b/libcxx/include/__ranges/chunk_view.h
@@ -153,7 +153,8 @@ class chunk_view<_View>::__iterator {
   _LIBCPP_HIDE_FROM_ABI constexpr __iterator(
       _Parent* __parent, iterator_t<_Base> __current, range_difference_t<_Base> __missing = 0)
       : __current_(std::move(__current)),
-        __end_(ranges::end(__parent->__base_), __n_(__parent->__n_)),
+        __end_(ranges::end(__parent->__base_)),
+        __n_(__parent->__n_),
         __missing_(__missing) {}
 
 public:
diff --git a/libcxx/test/libcxx/ranges/range.adaptors/range.chunk/adaptor.nodiscard.verify.cpp b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk/adaptor.nodiscard.verify.cpp
new file mode 100644
index 00000000000000..fd61fc09960cfc
--- /dev/null
+++ b/libcxx/test/libcxx/ranges/range.adaptors/range.chunk/adaptor.nodiscard.verify.cpp
@@ -0,0 +1,24 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 is marked as [[nodiscard]].
+
+#include <ranges>
+
+void test() {
+  int range[] = {1, 2, 3, 0, 1, 2};
+
+  std::views::chunk(2); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::views::chunk(range, 2); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  range | std::views::chunk(2); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+  std::views::all | std::views::chunk(2); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.chunk/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk/adaptor.pass.cpp
new file mode 100644
index 00000000000000..7254e9a4ca2809
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk/adaptor.pass.cpp
@@ -0,0 +1,192 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+#include <algorithm>
+#include <cassert>
+#include <concepts>
+#include <ranges>
+#include <type_traits>
+#include <iostream>
+
+#include "test_iterators.h"
+#include "test_range.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_;
+};
+
+template <typename View>
+constexpr void compare_views(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) {
+    const bool eq = std::ranges::equal(*b1, *b2, [](const auto a, const auto b) {
+      assert(a == b);
+      return true;
+    });
+    assert(eq);
+  }
+  assert(b1 == e1);
+  assert(b2 == e2);
+}
+
+template <class T>
+constexpr const T&& as_const_rvalue(T&& t) {
+  return static_cast<T const&&>(t);
+}
+
+constexpr bool test() {
+  constexpr int N = 10;
+  int buf[N]      = {1, 1, 4, 5, 1, 4, 1, 9, 1, 9};
+
+  // Test range adaptor object
+  {
+    using RangeAdaptorObject = decltype(std::views::chunk);
+    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_t<RangeAdaptorObject>>);
+  }
+
+  // Test `views::chunk(n)(range)`
+  {
+    using Result = std::ranges::chunk_view<Range>;
+    const Range range(buf, buf + N);
+
+    {
+      // `views::chunk(n)` - &&
+      std::same_as<Result> decltype(auto) result = std::views::chunk(3)(range);
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+    {
+      // `views::chunk(n)` - const&&
+      std::same_as<Result> decltype(auto) result = as_const_rvalue(std::views::chunk(3))(range);
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+    {
+      // `views::chunk(n)` - &
+      auto partial                               = std::views::chunk(3);
+      std::same_as<Result> decltype(auto) result = partial(range);
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+    {
+      // `views::chunk(n)` - const&
+      const auto partial                         = std::views::chunk(3);
+      std::same_as<Result> decltype(auto) result = partial(range);
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+  }
+
+  // Test `range | views::chunk(n)`
+  {
+    using Result = std::ranges::chunk_view<Range>;
+    const Range range(buf, buf + N);
+
+    {
+      // `views::chunk(n)` - &&
+      std::same_as<Result> decltype(auto) result = range | std::views::chunk(3);
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+    {
+      // `views::chunk(n)` - const&&
+      std::same_as<Result> decltype(auto) result = range | as_const_rvalue(std::views::chunk(3));
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+    {
+      // `views::chunk(n)` - &
+      auto partial                               = std::views::chunk(3);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+    {
+      // `views::chunk(n)` - const&
+      const auto partial                         = std::views::chunk(3);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+  }
+
+  // Test `views::chunk(range, n)` range adaptor object
+  {
+    using Result = std::ranges::chunk_view<Range>;
+    const Range range(buf, buf + N);
+
+    {
+      // `views::chunk` - &&
+      auto range_adaptor                         = std::views::chunk;
+      std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, 3);
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+    {
+      // `views::chunk` - const&&
+      auto const range_adaptor                   = std::views::chunk;
+      std::same_as<Result> decltype(auto) result = std::move(range_adaptor)(range, 3);
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+    {
+      // `views::chunk` - &
+      auto range_adaptor                         = std::views::chunk;
+      std::same_as<Result> decltype(auto) result = range_adaptor(range, 3);
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+    {
+      // `views::chunk` - const&
+      auto const range_adaptor                   = std::views::chunk;
+      std::same_as<Result> decltype(auto) result = range_adaptor(range, 3);
+      compare_views(result, {{1, 1, 4}, {5, 1, 4}, {1, 9, 1}, {9}});
+    }
+  }
+
+  // Test that it's possible to call `std::views::chunk` with any single argument as long as the resulting closure is
+  // never invoked. There is no good use case for it, but it's valid.
+  {
+    struct X {};
+    [[maybe_unused]] auto partial = std::views::chunk(X{});
+  }
+
+  // Test SFINAE friendliness
+  {
+    struct NotAView {};
+
+    static_assert(!CanBePiped<Range, decltype(std::views::chunk)>);
+    static_assert(CanBePiped<Range, decltype(std::views::chunk(N))>);
+    static_assert(!CanBePiped<NotAView, decltype(std::views::chunk(N))>);
+    static_assert(!CanBePiped<std::initializer_list<int>, decltype(std::views::chunk(N))>);
+
+    static_assert(!std::is_invocable_v<decltype(std::views::chunk)>);
+    static_assert(std::is_invocable_v<decltype(std::views::chunk), Range, std::ranges::range_difference_t<Range>>);
+    static_assert(!std::is_invocable_v<decltype(std::views::chunk), NotAView, std::ranges::range_difference_t<Range>>);
+  }
+
+  { static_assert(std::is_same_v<decltype(std::ranges::views::chunk), decltype(std::views::chunk)>); }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.chunk/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk/base.pass.cpp
new file mode 100644
index 00000000000000..404f019207df55
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk/base.pass.cpp
@@ -0,0 +1,98 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17, 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) : was_copy_initialized(true), begin_(other.begin_), end_(other.end_) {}
+  constexpr Range(Range&& other) : was_move_initialized(true), begin_(other.begin_), end_(other.end_) {}
+  Range& operator=(Range const&) = default;
+  Range& operator=(Range&&)      = default;
+  constexpr int* begin() const { return begin_; }
+  constexpr int* end() const { return end_; }
+
+  bool was_copy_initialized = false;
+  bool was_move_initialized = false;
+
+private:
+  int* begin_;
+  int* end_;
+};
+
+static_assert(std::ranges::view<Range>);
+static_assert(std::ranges::forward_range<Range>);
+
+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() {
+  constexpr int N = 4;
+  int buf[N]      = {1, 2, 3, 4};
+
+  // Check the const& overload
+  {
+    Range range(buf, buf + N);
+    const std::ranges::chunk_view<Range> view(range, N);
+    std::same_as<Range> decltype(auto) result = view.base();
+    assert(result.was_copy_initialized);
+    assert(result.begin() == buf);
+    assert(result.end() == buf + N);
+  }
+
+  // Check the && overload
+  {
+    Range range(buf, buf + N);
+    std::ranges::chunk_view<Range> view(range, N);
+    std::same_as<Range> decltype(auto) result = std::move(view).base();
+    assert(result.was_move_initialized);
+    assert(result.begin() == buf);
+    assert(result.end() == buf + N);
+  }
+
+  // Ensure the const& overloads are not considered when the base is not copy-constructible
+  {
+    static_assert(!CanCallBaseOn<const std::ranges::chunk_view<NonCopyableRange>&>);
+    static_assert(!CanCallBaseOn<std::ranges::chunk_view<NonCopyableRange>&>);
+    static_assert(!CanCallBaseOn<const std::ranges::chunk_view<NonCopyableRange>&&>);
+    static_assert(CanCallBaseOn<std::ranges::chunk_view<NonCopyableRange>&&>);
+    static_assert(CanCallBaseOn<std::ranges::chunk_view<NonCopyableRange>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.chunk/ctad.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.chunk/ctad.pass.cpp
new file mode 100644
index 00000000000000..e96717462df5e1
--- /dev/null
+++ b/libcxx/test/std/ranges/range.adaptors/range.chunk/ctad.pass.cpp
@@ -0,0 +1,62 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 _View>
+// chunk_view(_View&&, range_difference_t<_View>) -> chunk_view<views::all_t<_View>>;
+
+#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>);
+
+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>);
+
+constexpr bool test() {
+  {
+    View v;
+    std::ranges::chunk_view view(v, 42);
+    static_assert(std::is_same_v<decltype(view), std::ranges::chunk_view<View>>);
+  }
+  {
+    Range r;
+    std::ranges::chunk_view view(r, 42);
+    static_assert(std::is_same_v<decltype(view), std::ranges::chunk_view<std::ranges::ref_view<Range>>>);
+  }
+  {
+    std::ranges::chunk_view view(Range{}, 42);
+    static_assert(std::is_same_v<decltype(view), std::ranges::chunk_view<std::ranges::owning_view<Range>>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}



More information about the libcxx-commits mailing list