libstdc++: Implement ranges::zip_view from P2321R2
authorPatrick Palka <ppalka@redhat.com>
Wed, 24 Aug 2022 23:17:27 +0000 (19:17 -0400)
committerPatrick Palka <ppalka@redhat.com>
Wed, 24 Aug 2022 23:17:27 +0000 (19:17 -0400)
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
libstdc++-v3/include/bits/ranges_util.h
libstdc++-v3/include/std/ranges
libstdc++-v3/testsuite/std/ranges/zip/1.cc [new file with mode: 0644]

index 3d30fb1..2a11636 100644 (file)
@@ -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
   {
index 37d7bc8..bb56dee 100644 (file)
@@ -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;
index 3e71ecb..c600bad 100644 (file)
@@ -4352,6 +4352,448 @@ 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> {
+                            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> {
+                            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 (file)
index 0000000..0113efd
--- /dev/null
@@ -0,0 +1,111 @@
+// { 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});
+  auto i2 = z2.begin();
+  i2 += 1;
+  i2 -= -1;
+  VERIFY( i2 == z2.end() );
+  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() );
+  auto it = z.begin();
+  ++it;
+  it++;
+  it--;
+  --it;
+  VERIFY( it == z.begin() );
+
+  return true;
+}
+
+int
+main()
+{
+  static_assert(test01());
+  static_assert(test02());
+  static_assert(test03());
+}