[3/3] libstdc++: Implement ranges::zip_view from P2321R2

Message ID 20220823013500.1756466-3-ppalka@redhat.com
State New, archived
Headers
Series [1/3] libstdc++: Separate construct/convertibility tests for std::tuple |

Commit Message

Patrick Palka Aug. 23, 2022, 1:35 a.m. UTC
  Tested on 86_64-pc-linux-gnu, does this look OK for trunk?

libstdc++-v3/ChangeLog:

	* include/bits/ranges_algo.h (__min_fn, min): Move to ...
	* include/bits/ranges_util.h: ... here in order to avoid
	including all of ranges_algo.h from <ranges>.
	* include/std/ranges (__detail::__zip_is_common): Define for
	C++23 as per P2321R2.
	(__detail::__tuple_or_pair): Likewise.
	(__detail::__tuple_or_pair_t): Likewise.
	(__detail::__tuple_transform): Likewise.
	(__detail::__tuple_for_each): Likewise.
	(zip_view): Likewise.
	(enable_borrowed_range<zip_view>): Likewise.
	(__detail::__all_random_access): Likewise.
	(__detail::__all_bidirectional): Likewise.
	(__detail::__all_forward): Likewise.
	(__detail::__zip_view_iter_cat): Likewise.
	(zip_view::iterator): Likewise.
	(zip_view::sentinel): Likewise.
	(testsuite/std/ranges/zip/1.cc): New test.
---
 libstdc++-v3/include/bits/ranges_algo.h    |  54 +--
 libstdc++-v3/include/bits/ranges_util.h    |  55 +++
 libstdc++-v3/include/std/ranges            | 445 +++++++++++++++++++++
 libstdc++-v3/testsuite/std/ranges/zip/1.cc | 101 +++++
 4 files changed, 602 insertions(+), 53 deletions(-)
 create mode 100644 libstdc++-v3/testsuite/std/ranges/zip/1.cc
  

Comments

Jonathan Wakely Aug. 24, 2022, 12:15 p.m. UTC | #1
On Tue, 23 Aug 2022 at 02:38, Patrick Palka via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
>
> Tested on 86_64-pc-linux-gnu, does this look OK for trunk?

As discussed privately, please remove the #include <cstdint> (which is
not needed, and adds declarations to the global namespace that we
don't need).

OK with that change, thanks.

N.B. re the TODOs, we already have a constexpr abs in <numeric> but
it's probably not worth including that just for one function, that
you've already managed without.
  
Jonathan Wakely Aug. 26, 2022, 8:05 p.m. UTC | #2
On Tue, 23 Aug 2022 at 02:38, Patrick Palka via Libstdc++
<libstdc++@gcc.gnu.org> wrote:
> +    template<typename _Fp, typename _Tuple>
> +      constexpr void
> +      __tuple_for_each(_Fp&& __f, _Tuple&& __tuple)
> +      {
> +       std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
> +         (std::__invoke(__f, std::forward<_Ts>(__elts)), ...);
> +       }, std::forward<_Tuple>(__tuple));


Nicole Mazzuca noticed a problem with the definition of
tuple-for-each, which I've just added to the issues list:
https://cplusplus.github.io/LWG/issue3755
  
Jonathan Wakely Aug. 31, 2022, 10:12 a.m. UTC | #3
On Fri, 26 Aug 2022 at 21:05, Jonathan Wakely wrote:
>
> On Tue, 23 Aug 2022 at 02:38, Patrick Palka via Libstdc++
> <libstdc++@gcc.gnu.org> wrote:
> > +    template<typename _Fp, typename _Tuple>
> > +      constexpr void
> > +      __tuple_for_each(_Fp&& __f, _Tuple&& __tuple)
> > +      {
> > +       std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
> > +         (std::__invoke(__f, std::forward<_Ts>(__elts)), ...);
> > +       }, std::forward<_Tuple>(__tuple));
>
>
> Nicole Mazzuca noticed a problem with the definition of
> tuple-for-each, which I've just added to the issues list:
> https://cplusplus.github.io/LWG/issue3755

Ignore this. The __tuple_for_each function is only ever called by the
library, so there's no way for something like Evil to be passed to it.
  

Patch

diff --git a/libstdc++-v3/include/bits/ranges_algo.h b/libstdc++-v3/include/bits/ranges_algo.h
index 3d30fb1428c..2a116361a67 100644
--- a/libstdc++-v3/include/bits/ranges_algo.h
+++ b/libstdc++-v3/include/bits/ranges_algo.h
@@ -2902,59 +2902,7 @@  namespace ranges
 
   inline constexpr __set_symmetric_difference_fn set_symmetric_difference{};
 
-  struct __min_fn
-  {
-    template<typename _Tp, typename _Proj = identity,
-	     indirect_strict_weak_order<projected<const _Tp*, _Proj>>
-	       _Comp = ranges::less>
-      constexpr const _Tp&
-      operator()(const _Tp& __a, const _Tp& __b,
-		 _Comp __comp = {}, _Proj __proj = {}) const
-      {
-	if (std::__invoke(__comp,
-			  std::__invoke(__proj, __b),
-			  std::__invoke(__proj, __a)))
-	  return __b;
-	else
-	  return __a;
-      }
-
-    template<input_range _Range, typename _Proj = identity,
-	     indirect_strict_weak_order<projected<iterator_t<_Range>, _Proj>>
-	       _Comp = ranges::less>
-      requires indirectly_copyable_storable<iterator_t<_Range>,
-					    range_value_t<_Range>*>
-      constexpr range_value_t<_Range>
-      operator()(_Range&& __r, _Comp __comp = {}, _Proj __proj = {}) const
-      {
-	auto __first = ranges::begin(__r);
-	auto __last = ranges::end(__r);
-	__glibcxx_assert(__first != __last);
-	auto __result = *__first;
-	while (++__first != __last)
-	  {
-	    auto __tmp = *__first;
-	    if (std::__invoke(__comp,
-			      std::__invoke(__proj, __tmp),
-			      std::__invoke(__proj, __result)))
-	      __result = std::move(__tmp);
-	  }
-	return __result;
-      }
-
-    template<copyable _Tp, typename _Proj = identity,
-	     indirect_strict_weak_order<projected<const _Tp*, _Proj>>
-	       _Comp = ranges::less>
-      constexpr _Tp
-      operator()(initializer_list<_Tp> __r,
-		 _Comp __comp = {}, _Proj __proj = {}) const
-      {
-	return (*this)(ranges::subrange(__r),
-		       std::move(__comp), std::move(__proj));
-      }
-  };
-
-  inline constexpr __min_fn min{};
+  // min is defined in <bits/ranges_util.h>.
 
   struct __max_fn
   {
diff --git a/libstdc++-v3/include/bits/ranges_util.h b/libstdc++-v3/include/bits/ranges_util.h
index 37d7bc862f9..bb56deee01b 100644
--- a/libstdc++-v3/include/bits/ranges_util.h
+++ b/libstdc++-v3/include/bits/ranges_util.h
@@ -649,6 +649,61 @@  namespace ranges
   };
 
   inline constexpr __search_fn search{};
+
+  struct __min_fn
+  {
+    template<typename _Tp, typename _Proj = identity,
+	     indirect_strict_weak_order<projected<const _Tp*, _Proj>>
+	       _Comp = ranges::less>
+      constexpr const _Tp&
+      operator()(const _Tp& __a, const _Tp& __b,
+		 _Comp __comp = {}, _Proj __proj = {}) const
+      {
+	if (std::__invoke(__comp,
+			  std::__invoke(__proj, __b),
+			  std::__invoke(__proj, __a)))
+	  return __b;
+	else
+	  return __a;
+      }
+
+    template<input_range _Range, typename _Proj = identity,
+	     indirect_strict_weak_order<projected<iterator_t<_Range>, _Proj>>
+	       _Comp = ranges::less>
+      requires indirectly_copyable_storable<iterator_t<_Range>,
+					    range_value_t<_Range>*>
+      constexpr range_value_t<_Range>
+      operator()(_Range&& __r, _Comp __comp = {}, _Proj __proj = {}) const
+      {
+	auto __first = ranges::begin(__r);
+	auto __last = ranges::end(__r);
+	__glibcxx_assert(__first != __last);
+	auto __result = *__first;
+	while (++__first != __last)
+	  {
+	    auto __tmp = *__first;
+	    if (std::__invoke(__comp,
+			      std::__invoke(__proj, __tmp),
+			      std::__invoke(__proj, __result)))
+	      __result = std::move(__tmp);
+	  }
+	return __result;
+      }
+
+    template<copyable _Tp, typename _Proj = identity,
+	     indirect_strict_weak_order<projected<const _Tp*, _Proj>>
+	       _Comp = ranges::less>
+      constexpr _Tp
+      operator()(initializer_list<_Tp> __r,
+		 _Comp __comp = {}, _Proj __proj = {}) const
+      {
+	return (*this)(ranges::subrange(__r),
+		       std::move(__comp), std::move(__proj));
+      }
+  };
+
+  inline constexpr __min_fn min{};
+
 } // namespace ranges
 
   using ranges::get;
diff --git a/libstdc++-v3/include/std/ranges b/libstdc++-v3/include/std/ranges
index 3e71ecb32b7..8d0c2a52b40 100644
--- a/libstdc++-v3/include/std/ranges
+++ b/libstdc++-v3/include/std/ranges
@@ -39,6 +39,7 @@ 
 #if __cpp_lib_concepts
 
 #include <compare>
+#include <cstdint>
 #include <initializer_list>
 #include <iterator>
 #include <optional>
@@ -4352,6 +4353,450 @@  namespace views::__adaptor
     inline constexpr auto values = elements<1>;
   } // namespace views
 
+#if __cplusplus > 202002L
+  namespace __detail
+  {
+    template<typename... _Rs>
+      concept __zip_is_common = (sizeof...(_Rs) == 1 && (common_range<_Rs> && ...))
+	|| (!(bidirectional_range<_Rs> && ...) && (common_range<_Rs> && ...))
+	|| ((random_access_range<_Rs> && ...) && (sized_range<_Rs> && ...));
+
+    template<typename... _Ts>
+      struct __tuple_or_pair
+      { using type = std::tuple<_Ts...>; };
+
+    template<typename _Tp, typename _Up>
+      struct __tuple_or_pair<_Tp, _Up>
+      { using type = pair<_Tp, _Up>; };
+
+    template<typename... _Ts>
+      using __tuple_or_pair_t = typename __tuple_or_pair<_Ts...>::type;
+
+    template<typename _Fp, typename _Tuple>
+      constexpr auto
+      __tuple_transform(_Fp&& __f, _Tuple&& __tuple)
+      {
+	return std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
+	  return __tuple_or_pair_t<invoke_result_t<_Fp&, _Ts>...>
+	    (std::__invoke(__f, std::forward<_Ts>(__elts))...);
+	}, std::forward<_Tuple>(__tuple));
+      }
+
+    template<typename _Fp, typename _Tuple>
+      constexpr void
+      __tuple_for_each(_Fp&& __f, _Tuple&& __tuple)
+      {
+	std::apply([&]<typename... _Ts>(_Ts&&... __elts) {
+	  (std::__invoke(__f, std::forward<_Ts>(__elts)), ...);
+	}, std::forward<_Tuple>(__tuple));
+      }
+  } // namespace __detail
+
+  template<input_range... _Vs>
+    requires (view<_Vs> && ...) && (sizeof...(_Vs) > 0)
+  class zip_view : public view_interface<zip_view<_Vs...>>
+  {
+    tuple<_Vs...> _M_views;
+
+    template<bool> class iterator;
+    template<bool> class sentinel;
+
+  public:
+    zip_view() = default;
+
+    constexpr explicit
+    zip_view(_Vs... views)
+      : _M_views(std::move(views)...)
+    { }
+
+    constexpr auto
+    begin() requires (!(__detail::__simple_view<_Vs> && ...))
+    { return iterator<false>(__detail::__tuple_transform(ranges::begin, _M_views)); }
+
+    constexpr auto
+    begin() const requires (range<const _Vs> && ...)
+    { return iterator<true>(__detail::__tuple_transform(ranges::begin, _M_views)); }
+
+    constexpr auto
+    end() requires (!(__detail::__simple_view<_Vs> && ...))
+    {
+      if constexpr (!__detail::__zip_is_common<_Vs...>)
+        return sentinel<false>(__detail::__tuple_transform(ranges::end, _M_views));
+      else if constexpr ((random_access_range<_Vs> && ...))
+        return begin() + iter_difference_t<iterator<false>>(size());
+      else
+        return iterator<false>(__detail::__tuple_transform(ranges::end, _M_views));
+    }
+
+    constexpr auto
+    end() const requires (range<const _Vs> && ...)
+    {
+      if constexpr (!__detail::__zip_is_common<const _Vs...>)
+        return sentinel<true>(__detail::__tuple_transform(ranges::end, _M_views));
+      else if constexpr ((random_access_range<const _Vs> && ...))
+        return begin() + iter_difference_t<iterator<true>>(size());
+      else
+        return iterator<true>(__detail::__tuple_transform(ranges::end, _M_views));
+    }
+
+    constexpr auto
+    size() requires (sized_range<_Vs> && ...)
+    {
+      return std::apply([](auto... sizes) {
+	using _CT = __detail::__make_unsigned_like_t<common_type_t<decltype(sizes)...>>;
+	return ranges::min({_CT(sizes)...});
+      }, __detail::__tuple_transform(ranges::size, _M_views));
+    }
+
+    constexpr auto
+    size() const requires (sized_range<const _Vs> && ...)
+    {
+      return std::apply([](auto... sizes) {
+	using _CT = __detail::__make_unsigned_like_t<common_type_t<decltype(sizes)...>>;
+	return ranges::min({_CT(sizes)...});
+      }, __detail::__tuple_transform(ranges::size, _M_views));
+    }
+  };
+
+  template<typename... _Rs>
+    zip_view(_Rs&&...) -> zip_view<views::all_t<_Rs>...>;
+
+  template<typename... _Views>
+    inline constexpr bool enable_borrowed_range<zip_view<_Views...>>
+      = (enable_borrowed_range<_Views> && ...);
+
+  namespace __detail
+  {
+    template<bool _Const, typename... _Vs>
+      concept __all_random_access
+	= (random_access_range<__maybe_const_t<_Const, _Vs>> && ...);
+
+    template<bool _Const, typename... _Vs>
+      concept __all_bidirectional
+	= (bidirectional_range<__maybe_const_t<_Const, _Vs>> && ...);
+
+    template<bool _Const, typename... _Vs>
+      concept __all_forward
+	= (forward_range<__maybe_const_t<_Const, _Vs>> && ...);
+
+    template<bool _Const, typename... _Views>
+      struct __zip_view_iter_cat
+      { };
+
+    template<bool _Const, typename... _Views>
+      requires __all_forward<_Const, _Views...>
+      struct __zip_view_iter_cat<_Const, _Views...>
+      { using iterator_category = input_iterator_tag; };
+  } // namespace __detail
+
+  template<input_range... _Vs>
+    requires (view<_Vs> && ...) && (sizeof...(_Vs) > 0)
+  template<bool _Const>
+  class zip_view<_Vs...>::iterator
+    : public __detail::__zip_view_iter_cat<_Const, _Vs...>
+  {
+    __detail::__tuple_or_pair_t<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_current;
+
+    constexpr explicit
+    iterator(decltype(_M_current) __current)
+      : _M_current(std::move(__current))
+    { }
+
+    static auto
+    _S_iter_concept()
+    {
+      if constexpr (__detail::__all_random_access<_Const, _Vs...>)
+	return random_access_iterator_tag{};
+      else if constexpr (__detail::__all_bidirectional<_Const, _Vs...>)
+	return bidirectional_iterator_tag{};
+      else if constexpr (__detail::__all_forward<_Const, _Vs...>)
+	return forward_iterator_tag{};
+      else
+	return input_iterator_tag{};
+    }
+
+  public:
+    // iterator_category defined in __zip_view_iter_cat
+    using iterator_concept = decltype(_S_iter_concept());
+    using value_type
+      = __detail::__tuple_or_pair_t<range_value_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
+    using difference_type
+      = common_type_t<range_difference_t<__detail::__maybe_const_t<_Const, _Vs>>...>;
+
+    iterator() = default;
+
+    constexpr
+    iterator(iterator<!_Const> __i)
+      requires _Const
+	&& (convertible_to<iterator_t<_Vs>,
+			   iterator_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+      : _M_current(std::move(__i._M_current))
+    { }
+
+    constexpr auto
+    operator*() const
+    {
+      auto __f = [](auto& __i) -> decltype(auto) {
+	return *__i;
+      };
+      return __detail::__tuple_transform(__f, _M_current);
+    }
+
+    constexpr iterator&
+    operator++()
+    {
+      __detail::__tuple_for_each([](auto& __i) { ++__i; }, _M_current);
+      return *this;
+    }
+
+    constexpr void
+    operator++(int)
+    { ++*this; }
+
+    constexpr iterator
+    operator++(int)
+      requires __detail::__all_forward<_Const, _Vs...>
+    {
+      auto __tmp = *this;
+      ++*this;
+      return __tmp;
+    }
+
+    constexpr iterator&
+    operator--()
+      requires __detail::__all_bidirectional<_Const, _Vs...>
+    {
+      __detail::__tuple_for_each([](auto& __i) { --__i; }, _M_current);
+      return *this;
+    }
+
+    constexpr iterator
+    operator--(int)
+      requires __detail::__all_bidirectional<_Const, _Vs...>
+    {
+      auto __tmp = *this;
+      --*this;
+      return __tmp;
+    }
+
+    constexpr iterator&
+    operator+=(difference_type __x)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __f = [&]<typename _It>(_It& __i) {
+	__i += iter_difference_t<_It>(__x);
+      };
+      __detail::__tuple_for_each(__f, _M_current);
+      return *this;
+    }
+
+    constexpr iterator&
+    operator-=(difference_type __x)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __f = [&]<typename _It>(_It& __i) {
+	__i -= iter_difference_t<_It>(__x);
+      };
+      __detail::__tuple_for_each(__f, _M_current);
+      return *this;
+    }
+
+    constexpr auto
+    operator[](difference_type __n) const
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __f = [&]<typename _It>(_It& __i) -> decltype(auto) {
+	return __i[iter_difference_t<_It>(__n)];
+      };
+      return __detail::__tuple_transform(__f, _M_current);
+    }
+
+    friend constexpr bool
+    operator==(const iterator& __x, const iterator& __y)
+      requires (equality_comparable<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+    {
+      if constexpr (__detail::__all_bidirectional<_Const, _Vs...>)
+	return __x._M_current == __y._M_current;
+      else
+	return [&]<size_t... _Is>(index_sequence<_Is...>) {
+	  return ((std::get<_Is>(__x._M_current) == std::get<_Is>(__y._M_current)) || ...);
+	}(make_index_sequence<sizeof...(_Vs)>{});
+    }
+
+    friend constexpr bool
+    operator<(const iterator& __x, const iterator& __y)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    { return __x._M_current < __y._M_current; }
+
+    friend constexpr bool
+    operator>(const iterator& __x, const iterator& __y)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    { return __y < __x; }
+
+    friend constexpr bool
+    operator<=(const iterator& __x, const iterator& __y)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    { return !(__y < __x); }
+
+    friend constexpr bool
+    operator>=(const iterator& __x, const iterator& __y)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    { return !(__x < __y); }
+
+    friend constexpr auto
+    operator<=>(const iterator& __x, const iterator& __y)
+      requires __detail::__all_random_access<_Const, _Vs...>
+	&& (three_way_comparable<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+    { return __x._M_current <=> __y._M_current; }
+
+    friend constexpr iterator
+    operator+(const iterator& __i, difference_type __n)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __r = __i;
+      __r += __n;
+      return __r;
+    }
+
+    friend constexpr iterator
+    operator+(difference_type __n, const iterator& __i)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __r = __i;
+      __r += __n;
+      return __r;
+    }
+
+    friend constexpr iterator
+    operator-(const iterator& __i, difference_type __n)
+      requires __detail::__all_random_access<_Const, _Vs...>
+    {
+      auto __r = __i;
+      __r -= __n;
+      return __r;
+    }
+
+    friend constexpr difference_type
+    operator-(const iterator& __x, const iterator& __y)
+      requires (sized_sentinel_for<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>,
+				   iterator_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+    {
+      return [&]<size_t... _Is>(index_sequence<_Is...>) {
+	return ranges::min({difference_type(std::get<_Is>(__x._M_current)
+					    - std::get<_Is>(__y._M_current))...},
+			   ranges::less{},
+			   [](difference_type __i) -> make_unsigned_t<difference_type> {
+			     // TODO: use constexpr std::abs from P0533R9 once implemented
+			     return __i < 0 ? -__i : __i;
+			   });
+      }(make_index_sequence<sizeof...(_Vs)>{});
+    }
+
+    friend constexpr auto
+    iter_move(const iterator& __i)
+    { return __detail::__tuple_transform(ranges::iter_move, __i._M_current); }
+
+    friend constexpr void
+    iter_swap(const iterator& __l, const iterator& __r)
+      requires (indirectly_swappable<iterator_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+    {
+      [&]<size_t... _Is>(index_sequence<_Is...>) {
+	(ranges::iter_swap(std::get<_Is>(__l._M_current), std::get<_Is>(__r._M_current)), ...);
+      }(make_index_sequence<sizeof...(_Vs)>{});
+    }
+
+    friend class zip_view;
+  };
+
+  template<input_range... _Vs>
+    requires (view<_Vs> && ...) && (sizeof...(_Vs) > 0)
+  template<bool _Const>
+  class zip_view<_Vs...>::sentinel
+  {
+    __detail::__tuple_or_pair_t<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>...> _M_end;
+
+    constexpr explicit
+    sentinel(decltype(_M_end) __end)
+      : _M_end(__end)
+    { }
+
+    friend class zip_view;
+
+  public:
+    sentinel() = default;
+
+    constexpr
+    sentinel(sentinel<!_Const> __i)
+      requires _Const
+	&& (convertible_to<sentinel_t<_Vs>,
+			   sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>> && ...)
+      : _M_end(std::move(__i._M_end))
+    { }
+
+    template<bool _OtherConst>
+      requires (sentinel_for<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>,
+			     iterator_t<__detail::__maybe_const_t<_OtherConst, _Vs>>> && ...)
+    friend constexpr bool
+    operator==(const iterator<_OtherConst>& __x, const sentinel& __y)
+    {
+      return [&]<size_t... _Is>(index_sequence<_Is...>) {
+	return ((std::get<_Is>(__x._M_current) == std::get<_Is>(__y._M_end)) || ...);
+      }(make_index_sequence<sizeof...(_Vs)>{});
+    }
+
+    template<bool _OtherConst>
+      requires (sized_sentinel_for<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>,
+				   iterator_t<__detail::__maybe_const_t<_OtherConst, _Vs>>> && ...)
+    friend constexpr auto
+    operator-(const iterator<_OtherConst>& __x, const sentinel& __y)
+    {
+      using _Ret
+	= common_type_t<range_difference_t<__detail::__maybe_const_t<_OtherConst, _Vs>>...>;
+      return [&]<size_t... _Is>(index_sequence<_Is...>) {
+	return ranges::min({_Ret(std::get<_Is>(__x._M_current) - std::get<_Is>(__y._M_end))...},
+			   ranges::less{},
+			   [](_Ret __i) -> make_unsigned_t<_Ret> {
+			     // TODO: use constexpr std::abs from P0533R9 once implemented
+			     return __i < 0 ? -__i : __i;
+			   });
+      }(make_index_sequence<sizeof...(_Vs)>{});
+    }
+
+    template<bool _OtherConst>
+      requires (sized_sentinel_for<sentinel_t<__detail::__maybe_const_t<_Const, _Vs>>,
+				   iterator_t<__detail::__maybe_const_t<_OtherConst, _Vs>>> && ...)
+    friend constexpr auto
+    operator-(const sentinel& __y, const iterator<_OtherConst>& __x)
+    { return -(__x - __y); }
+  };
+
+  namespace views
+  {
+    namespace __detail
+    {
+      template<typename... _Ts>
+	concept __can_zip_view
+	  = requires { zip_view<all_t<_Ts>...>(std::declval<_Ts>()...); };
+    }
+
+    struct _Zip
+    {
+      template<typename... _Ts>
+	requires (sizeof...(_Ts) == 0 || __detail::__can_zip_view<_Ts...>)
+	[[nodiscard]]
+	constexpr auto
+	operator()(_Ts&&... __ts) const
+	{
+	  if constexpr (sizeof...(_Ts) == 0)
+	    return views::empty<tuple<>>;
+	  else
+	    return zip_view<all_t<_Ts>...>(std::forward<_Ts>(__ts)...);
+	}
+    };
+
+    inline constexpr _Zip zip;
+  }
+#endif // C++23
 } // namespace ranges
 
   namespace views = ranges::views;
diff --git a/libstdc++-v3/testsuite/std/ranges/zip/1.cc b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
new file mode 100644
index 00000000000..4d5835829dd
--- /dev/null
+++ b/libstdc++-v3/testsuite/std/ranges/zip/1.cc
@@ -0,0 +1,101 @@ 
+// { dg-options "-std=gnu++23" }
+// { dg-do run { target c++23 } }
+
+#include <ranges>
+#include <algorithm>
+#include <utility>
+#include <vector>
+#include <testsuite_hooks.h>
+#include <testsuite_iterators.h>
+
+namespace ranges = std::ranges;
+namespace views = std::views;
+
+constexpr bool
+test01()
+{
+  static_assert(ranges::empty(views::zip()));
+  static_assert(ranges::empty(views::empty<int>));
+
+  auto z1 = views::zip(std::array{1, 2});
+  const auto i0 = z1.begin(), i1 = z1.begin() + 1;
+  VERIFY( i0 + 1 - 1 == i0 );
+  VERIFY( i0 < i1 );
+  VERIFY( i1 < z1.end() );
+  VERIFY( i1 - i0 == 1 );
+  VERIFY( i0 - i1 == -1 );
+  VERIFY( z1.end() - i1 == 1 );
+  VERIFY( i1 - z1.end() == -1 );
+  ranges::iter_swap(i0, i1);
+  VERIFY( ranges::equal(std::move(z1) | views::keys, (int[]){2, 1}) );
+
+  auto z2 = views::zip(std::array{1, 2}, std::array{3, 4, 5});
+  VERIFY( ranges::size(z2) == 2 );
+  VERIFY( ranges::size(std::as_const(z2)) == 2 );
+  VERIFY( z2[0].first == 1 && z2[0].second == 3 );
+  VERIFY( z2[1].first == 2 && z2[1].second == 4 );
+  for (const auto [x, y] : z2)
+    {
+      VERIFY( y - x == 2 );
+      std::swap(x, y);
+    }
+
+  int x[2] = {1, 2}, y[2] = {3, 4}, z[2] = {5, 6};
+  const auto z3 = views::zip(x, y, z);
+  VERIFY( ranges::size(z3) == 2 );
+  for (int i = 0; i < ranges::size(x); i++)
+    {
+      VERIFY( &std::get<0>(z3[i]) == &x[i] );
+      VERIFY( &std::get<1>(z3[i]) == &y[i] );
+      VERIFY( &std::get<2>(z3[i]) == &z[i] );
+    }
+
+  return true;
+}
+
+constexpr bool
+test02()
+{
+  using __gnu_test::test_input_range;
+  using __gnu_test::test_forward_range;
+  using __gnu_test::test_random_access_range;
+
+  using ty1 = ranges::zip_view<views::all_t<test_forward_range<int>>,
+			       views::all_t<test_random_access_range<int>>>;
+  static_assert(ranges::forward_range<ty1>);
+  static_assert(!ranges::random_access_range<ty1>);
+  static_assert(!ranges::sized_range<ty1>);
+
+  using ty2 = ranges::zip_view<views::all_t<test_forward_range<int>>,
+			       views::all_t<test_input_range<int>>,
+			       views::all_t<test_forward_range<int>>>;
+  static_assert(ranges::input_range<ty2>);
+  static_assert(!ranges::forward_range<ty2>);
+  static_assert(!ranges::sized_range<ty2>);
+
+  return true;
+}
+
+constexpr bool
+test03()
+{
+  int u[] = {1, 2, 3, 4}, v[] = {4, 5, 6}, w[] = {7, 8, 9, 10};
+  auto z = views::zip(u | views::filter([](auto) { return true; }), v, w);
+  using ty = decltype(z);
+  static_assert(ranges::forward_range<ty>);
+  static_assert(!ranges::common_range<ty>);
+  static_assert(!ranges::sized_range<ty>);
+  VERIFY( z.begin() == z.begin() );
+  VERIFY( z.begin() != z.end() );
+  VERIFY( ranges::next(z.begin(), 3) == z.end() );
+
+  return true;
+}
+
+int
+main()
+{
+  static_assert(test01());
+  static_assert(test02());
+  static_assert(test03());
+}