[libc++] Implement ranges::filter_view
authorLouis Dionne <ldionne.2@gmail.com>
Wed, 14 Jul 2021 21:01:25 +0000 (17:01 -0400)
committerLouis Dionne <ldionne.2@gmail.com>
Wed, 13 Apr 2022 13:03:46 +0000 (09:03 -0400)
Differential Revision: https://reviews.llvm.org/D109086

32 files changed:
libcxx/docs/Status/RangesPaper.csv
libcxx/include/CMakeLists.txt
libcxx/include/__ranges/filter_view.h [new file with mode: 0644]
libcxx/include/module.modulemap
libcxx/include/ranges
libcxx/test/libcxx/private_headers.verify.cpp
libcxx/test/std/library/description/conventions/customization.point.object/cpo.compile.pass.cpp
libcxx/test/std/ranges/range.adaptors/range.filter/adaptor.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/base.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/begin.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/constraints.compile.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/ctad.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/ctor.default.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/ctor.view_pred.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/end.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/iterator/arrow.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/iterator/base.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/iterator/compare.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/iterator/ctor.default.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/iterator/ctor.parent_iter.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/iterator/decrement.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/iterator/deref.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/iterator/increment.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/iterator/iter_move.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/iterator/iter_swap.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/iterator/types.compile.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/pred.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/base.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/compare.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/ctor.default.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/ctor.parent.pass.cpp [new file with mode: 0644]
libcxx/test/std/ranges/range.adaptors/range.filter/types.h [new file with mode: 0644]

index 6b08b5a..437ac1a 100644 (file)
@@ -151,7 +151,7 @@ Section,Description,Dependencies,Assignee,Complete
 `[range.iota] <https://wg21.link/range.iota>`_,`iota_view <https://llvm.org/D107396>`_,[range.all],Zoe Carver,✅
 `[range.all] <https://wg21.link/range.all>`_,`view::all <https://llvm.org/D102028>`_,"[range.subrange], [range.view.ref]",Zoe Carver,✅
 `[range.ref.view] <https://wg21.link/range.ref.view>`_,`ref_view <https://llvm.org/D102020>`_,[view.interface],Zoe Carver,✅
-`[range.filter] <https://wg21.link/range.filter>`_,`filter_view <https://llvm.org/D109086>`_,[range.all],Louis Dionne,Under review
+`[range.filter] <https://wg21.link/range.filter>`_,`filter_view <https://llvm.org/D109086>`_,[range.all],Louis Dionne,
 `[range.transform] <https://wg21.link/range.transform>`_,`transform_view <https://llvm.org/D103056>`_,[range.all],Zoe Carver,✅
 `[range.take] <https://wg21.link/range.take>`_,`take_view <https://llvm.org/D106507>`_,[range.all],Zoe Carver,✅
 `[range.join] <https://wg21.link/range.join>`_,`join_view <https://llvm.org/D107671>`_,[range.all],Zoe Carver,✅
index cf73417..3b62840 100644 (file)
@@ -371,6 +371,7 @@ set(files
   __ranges/empty_view.h
   __ranges/enable_borrowed_range.h
   __ranges/enable_view.h
+  __ranges/filter_view.h
   __ranges/iota_view.h
   __ranges/join_view.h
   __ranges/lazy_split_view.h
diff --git a/libcxx/include/__ranges/filter_view.h b/libcxx/include/__ranges/filter_view.h
new file mode 100644 (file)
index 0000000..b040ea5
--- /dev/null
@@ -0,0 +1,259 @@
+// -*- 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_FILTER_VIEW_H
+#define _LIBCPP___RANGES_FILTER_VIEW_H
+
+#include <__algorithm/ranges_find_if.h>
+#include <__config>
+#include <__debug>
+#include <__functional/bind_back.h>
+#include <__functional/invoke.h>
+#include <__functional/reference_wrapper.h>
+#include <__iterator/concepts.h>
+#include <__iterator/iter_move.h>
+#include <__iterator/iter_swap.h>
+#include <__iterator/iterator_traits.h>
+#include <__memory/addressof.h>
+#include <__ranges/access.h>
+#include <__ranges/all.h>
+#include <__ranges/concepts.h>
+#include <__ranges/copyable_box.h>
+#include <__ranges/non_propagating_cache.h>
+#include <__ranges/range_adaptor.h>
+#include <__ranges/view_interface.h>
+#include <__utility/forward.h>
+#include <__utility/in_place.h>
+#include <__utility/move.h>
+#include <concepts>
+#include <type_traits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+#  pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+namespace ranges {
+  template<input_range _View, indirect_unary_predicate<iterator_t<_View>> _Pred>
+    requires view<_View> && is_object_v<_Pred>
+  class filter_view : public view_interface<filter_view<_View, _Pred>> {
+    _LIBCPP_NO_UNIQUE_ADDRESS _View __base_ = _View();
+    _LIBCPP_NO_UNIQUE_ADDRESS __copyable_box<_Pred> __pred_;
+
+    // We cache the result of begin() to allow providing an amortized O(1) begin() whenever
+    // the underlying range is at least a forward_range.
+    static constexpr bool _UseCache = forward_range<_View>;
+    using _Cache = _If<_UseCache, __non_propagating_cache<iterator_t<_View>>, __empty_cache>;
+    _LIBCPP_NO_UNIQUE_ADDRESS _Cache __cached_begin_ = _Cache();
+
+    class __iterator;
+    class __sentinel;
+
+  public:
+    _LIBCPP_HIDE_FROM_ABI
+    filter_view() requires default_initializable<_View> && default_initializable<_Pred> = default;
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr filter_view(_View __base, _Pred __pred)
+      : __base_(std::move(__base)), __pred_(in_place, std::move(__pred))
+    { }
+
+    template<class _Vp = _View>
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr _View base() const& requires copy_constructible<_Vp> { return __base_; }
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr _View base() && { return std::move(__base_); }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr _Pred const& pred() const { return *__pred_; }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __iterator begin() {
+      _LIBCPP_ASSERT(__pred_.__has_value(), "Trying to call begin() on a filter_view that does not have a valid predicate.");
+      if constexpr (_UseCache) {
+        if (!__cached_begin_.__has_value()) {
+          __cached_begin_.__emplace(ranges::find_if(__base_, std::ref(*__pred_)));
+        }
+        return {*this, *__cached_begin_};
+      } else {
+        return {*this, ranges::find_if(__base_, std::ref(*__pred_))};
+      }
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr auto end() {
+      if constexpr (common_range<_View>)
+        return __iterator{*this, ranges::end(__base_)};
+      else
+        return __sentinel{*this};
+    }
+  };
+
+  template<class _Range, class _Pred>
+  filter_view(_Range&&, _Pred) -> filter_view<views::all_t<_Range>, _Pred>;
+
+  template<class _View>
+  struct __filter_iterator_category { };
+
+  template<forward_range _View>
+  struct __filter_iterator_category<_View> {
+    using _Cat = typename iterator_traits<iterator_t<_View>>::iterator_category;
+    using iterator_category =
+      _If<derived_from<_Cat, bidirectional_iterator_tag>, bidirectional_iterator_tag,
+      _If<derived_from<_Cat, forward_iterator_tag>,       forward_iterator_tag,
+      /* else */                                          _Cat
+    >>;
+  };
+
+  template<input_range _View, indirect_unary_predicate<iterator_t<_View>> _Pred>
+    requires view<_View> && is_object_v<_Pred>
+  class filter_view<_View, _Pred>::__iterator : public __filter_iterator_category<_View> {
+  public:
+    _LIBCPP_NO_UNIQUE_ADDRESS iterator_t<_View> __current_ = iterator_t<_View>();
+    _LIBCPP_NO_UNIQUE_ADDRESS filter_view* __parent_ = nullptr;
+
+    using iterator_concept =
+      _If<bidirectional_range<_View>, bidirectional_iterator_tag,
+      _If<forward_range<_View>,       forward_iterator_tag,
+      /* else */                      input_iterator_tag
+    >>;
+    // using iterator_category = inherited;
+    using value_type = range_value_t<_View>;
+    using difference_type = range_difference_t<_View>;
+
+    _LIBCPP_HIDE_FROM_ABI
+    __iterator() requires default_initializable<iterator_t<_View>> = default;
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __iterator(filter_view& __parent, iterator_t<_View> __current)
+      : __current_(std::move(__current)), __parent_(std::addressof(__parent))
+    { }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr iterator_t<_View> const& base() const& noexcept { return __current_; }
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr iterator_t<_View> base() && { return std::move(__current_); }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr range_reference_t<_View> operator*() const { return *__current_; }
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr iterator_t<_View> operator->() const
+      requires __has_arrow<iterator_t<_View>> && copyable<iterator_t<_View>>
+    {
+      return __current_;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __iterator& operator++() {
+      __current_ = ranges::find_if(std::move(++__current_), ranges::end(__parent_->__base_),
+                                   std::ref(*__parent_->__pred_));
+      return *this;
+    }
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr void operator++(int) { ++*this; }
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __iterator operator++(int) requires forward_range<_View> {
+      auto __tmp = *this;
+      ++*this;
+      return __tmp;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr __iterator& operator--() requires bidirectional_range<_View> {
+      do {
+        --__current_;
+      } while (!std::invoke(*__parent_->__pred_, *__current_));
+      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==(__iterator const& __x, __iterator const& __y)
+      requires equality_comparable<iterator_t<_View>>
+    {
+      return __x.__current_ == __y.__current_;
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    friend constexpr range_rvalue_reference_t<_View> iter_move(__iterator const& __it)
+      noexcept(noexcept(ranges::iter_move(__it.__current_)))
+    {
+      return ranges::iter_move(__it.__current_);
+    }
+
+    _LIBCPP_HIDE_FROM_ABI
+    friend constexpr void iter_swap(__iterator const& __x, __iterator const& __y)
+      noexcept(noexcept(ranges::iter_swap(__x.__current_, __y.__current_)))
+      requires indirectly_swappable<iterator_t<_View>>
+    {
+      return ranges::iter_swap(__x.__current_, __y.__current_);
+    }
+  };
+
+  template<input_range _View, indirect_unary_predicate<iterator_t<_View>> _Pred>
+    requires view<_View> && is_object_v<_Pred>
+  class filter_view<_View, _Pred>::__sentinel {
+  public:
+    sentinel_t<_View> __end_ = sentinel_t<_View>();
+
+    _LIBCPP_HIDE_FROM_ABI
+    __sentinel() = default;
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr explicit __sentinel(filter_view& __parent)
+      : __end_(ranges::end(__parent.__base_))
+    { }
+
+    _LIBCPP_HIDE_FROM_ABI
+    constexpr sentinel_t<_View> base() const { return __end_; }
+
+    _LIBCPP_HIDE_FROM_ABI
+    friend constexpr bool operator==(__iterator const& __x, __sentinel const& __y) {
+      return __x.__current_ == __y.__end_;
+    }
+  };
+
+namespace views {
+namespace __filter {
+  struct __fn {
+    template<class _Range, class _Pred>
+    [[nodiscard]] _LIBCPP_HIDE_FROM_ABI
+    constexpr auto operator()(_Range&& __range, _Pred&& __pred) const
+      noexcept(noexcept(filter_view(std::forward<_Range>(__range), std::forward<_Pred>(__pred))))
+      -> decltype(      filter_view(std::forward<_Range>(__range), std::forward<_Pred>(__pred)))
+      { return          filter_view(std::forward<_Range>(__range), std::forward<_Pred>(__pred)); }
+
+    template<class _Pred>
+      requires constructible_from<decay_t<_Pred>, _Pred>
+    [[nodiscard]] _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 __filter
+
+inline namespace __cpo {
+  inline constexpr auto filter = __filter::__fn{};
+} // namespace __cpo
+} // namespace views
+
+} // namespace ranges
+
+#endif // _LIBCPP_STD_VER > 17 && !defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES)
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___RANGES_FILTER_VIEW_H
index 6a20165..6cfa65c 100644 (file)
@@ -849,6 +849,7 @@ module std [system] {
       module empty_view             { private header "__ranges/empty_view.h" }
       module enable_borrowed_range  { private header "__ranges/enable_borrowed_range.h" }
       module enable_view            { private header "__ranges/enable_view.h" }
+      module filter_view            { private header "__ranges/filter_view.h" }
       module iota_view              { private header "__ranges/iota_view.h" }
       module join_view              { private header "__ranges/join_view.h" }
       module lazy_split_view        { private header "__ranges/lazy_split_view.h" }
index 3e843fc..aa945f1 100644 (file)
@@ -150,6 +150,15 @@ namespace std::ranges {
   template<class T>
     inline constexpr bool enable_borrowed_range<owning_view<T>> = enable_borrowed_range<T>;
 
+  // [range.filter], filter view
+  template<input_range V, indirect_unary_predicate<iterator_t<V>> Pred>
+    requires view<V> && is_object_v<Pred>
+  class filter_view;
+
+  namespace views {
+    inline constexpr unspecified filter = unspecified;
+  }
+
   // [range.drop], drop view
   template<view V>
     class drop_view;
@@ -266,6 +275,7 @@ namespace std {
 #include <__ranges/empty_view.h>
 #include <__ranges/enable_borrowed_range.h>
 #include <__ranges/enable_view.h>
+#include <__ranges/filter_view.h>
 #include <__ranges/iota_view.h>
 #include <__ranges/join_view.h>
 #include <__ranges/lazy_split_view.h>
index 33d7881..acb665e 100644 (file)
@@ -402,6 +402,7 @@ END-SCRIPT
 #include <__ranges/empty_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/empty_view.h'}}
 #include <__ranges/enable_borrowed_range.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/enable_borrowed_range.h'}}
 #include <__ranges/enable_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/enable_view.h'}}
+#include <__ranges/filter_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/filter_view.h'}}
 #include <__ranges/iota_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/iota_view.h'}}
 #include <__ranges/join_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/join_view.h'}}
 #include <__ranges/lazy_split_view.h> // expected-error@*:* {{use of private header from outside its module: '__ranges/lazy_split_view.h'}}
index 7ef68ec..e8af0fe 100644 (file)
@@ -89,7 +89,7 @@ static_assert(test(std::views::counted, a, 10));
 //static_assert(test(std::views::drop, a, 10));
 //static_assert(test(std::views::drop_while, a, [](int x){ return x < 10; }));
 //static_assert(test(std::views::elements<0>, pairs));
-//static_assert(test(std::views::filter, a, [](int x){ return x < 10; }));
+static_assert(test(std::views::filter, a, [](int x){ return x < 10; }));
 //static_assert(test(std::views::join, arrays));
 static_assert(test(std::views::lazy_split, a, 4));
 static_assert(test(std::views::reverse, a));
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/adaptor.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/adaptor.pass.cpp
new file mode 100644 (file)
index 0000000..c2a1ffc
--- /dev/null
@@ -0,0 +1,170 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// std::views::filter
+
+#include <ranges>
+
+#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 NonCopyablePredicate {
+  NonCopyablePredicate(NonCopyablePredicate const&) = delete;
+  template <class T>
+  constexpr bool operator()(T x) const { return x % 2 == 0; }
+};
+
+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 Pred {
+  constexpr bool operator()(int i) const { return i % 2 == 0; }
+};
+
+template <typename View>
+constexpr void compareViews(View v, 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) {
+    assert(*b1 == *b2);
+  }
+  assert(b1 == e1);
+  assert(b2 == e2);
+}
+
+constexpr bool test() {
+  int buff[] = {0, 1, 2, 3, 4, 5, 6, 7};
+
+  // Test `views::filter(pred)(v)`
+  {
+    using Result = std::ranges::filter_view<Range, Pred>;
+    Range const range(buff, buff + 8);
+    Pred pred;
+
+    {
+      std::same_as<Result> decltype(auto) result = std::views::filter(pred)(range);
+      compareViews(result, {0, 2, 4, 6});
+    }
+    {
+      auto const partial = std::views::filter(pred);
+      std::same_as<Result> decltype(auto) result = partial(range);
+      compareViews(result, {0, 2, 4, 6});
+    }
+  }
+
+  // Test `v | views::filter(pred)`
+  {
+    using Result = std::ranges::filter_view<Range, Pred>;
+    Range const range(buff, buff + 8);
+    Pred pred;
+
+    {
+      std::same_as<Result> decltype(auto) result = range | std::views::filter(pred);
+      compareViews(result, {0, 2, 4, 6});
+    }
+    {
+      auto const partial = std::views::filter(pred);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compareViews(result, {0, 2, 4, 6});
+    }
+  }
+
+  // Test `views::filter(v, pred)`
+  {
+    using Result = std::ranges::filter_view<Range, Pred>;
+    Range const range(buff, buff + 8);
+    Pred pred;
+
+    std::same_as<Result> decltype(auto) result = std::views::filter(range, pred);
+    compareViews(result, {0, 2, 4, 6});
+  }
+
+  // Test that one can call std::views::filter 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.
+  {
+    struct X { };
+    [[maybe_unused]] auto partial = std::views::filter(X{});
+  }
+
+  // Test `adaptor | views::filter(pred)`
+  {
+    Range const range(buff, buff + 8);
+
+    {
+      auto pred1 = [](int i) { return i % 2 == 0; };
+      auto pred2 = [](int i) { return i % 3 == 0; };
+      using Result = std::ranges::filter_view<std::ranges::filter_view<Range, decltype(pred1)>, decltype(pred2)>;
+      std::same_as<Result> decltype(auto) result = range | std::views::filter(pred1) | std::views::filter(pred2);
+      compareViews(result, {0, 6});
+    }
+    {
+      auto pred1 = [](int i) { return i % 2 == 0; };
+      auto pred2 = [](int i) { return i % 3 == 0; };
+      using Result = std::ranges::filter_view<std::ranges::filter_view<Range, decltype(pred1)>, decltype(pred2)>;
+      auto const partial = std::views::filter(pred1) | std::views::filter(pred2);
+      std::same_as<Result> decltype(auto) result = range | partial;
+      compareViews(result, {0, 6});
+    }
+  }
+
+  // Test SFINAE friendliness
+  {
+    struct NotAView { };
+    struct NotInvocable { };
+
+    static_assert(!CanBePiped<Range,    decltype(std::views::filter)>);
+    static_assert( CanBePiped<Range,    decltype(std::views::filter(Pred{}))>);
+    static_assert(!CanBePiped<NotAView, decltype(std::views::filter(Pred{}))>);
+    static_assert(!CanBePiped<Range,    decltype(std::views::filter(NotInvocable{}))>);
+
+    static_assert(!std::is_invocable_v<decltype(std::views::filter)>);
+    static_assert(!std::is_invocable_v<decltype(std::views::filter), Pred, Range>);
+    static_assert( std::is_invocable_v<decltype(std::views::filter), Range, Pred>);
+    static_assert(!std::is_invocable_v<decltype(std::views::filter), Range, Pred, Pred>);
+    static_assert(!std::is_invocable_v<decltype(std::views::filter), NonCopyablePredicate>);
+  }
+
+  {
+    static_assert(std::is_same_v<decltype(std::ranges::views::filter), decltype(std::views::filter)>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/base.pass.cpp
new file mode 100644 (file)
index 0000000..61f4d2e
--- /dev/null
@@ -0,0 +1,92 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-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;
+};
+
+struct Pred {
+  bool operator()(int) const;
+};
+
+struct NoCopyRange : std::ranges::view_base {
+  explicit NoCopyRange(int*, int*);
+  NoCopyRange(NoCopyRange const&) = delete;
+  NoCopyRange(NoCopyRange&&) = default;
+  NoCopyRange& operator=(NoCopyRange const&) = default;
+  NoCopyRange& operator=(NoCopyRange&&) = default;
+  int* begin() const;
+  int* end() const;
+};
+
+template <typename T>
+concept can_call_base_on = requires(T t) { std::forward<T>(t).base(); };
+
+constexpr bool test() {
+  int buff[] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+  // Check the const& overload
+  {
+    Range range(buff, buff + 8);
+    std::ranges::filter_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 + 8);
+  }
+
+  // Check the && overload
+  {
+    Range range(buff, buff + 8);
+    std::ranges::filter_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 + 8);
+  }
+
+  // Ensure the const& overload is not considered when the base is not copy-constructible
+  {
+    static_assert(!can_call_base_on<std::ranges::filter_view<NoCopyRange, Pred> const&>);
+    static_assert(!can_call_base_on<std::ranges::filter_view<NoCopyRange, Pred>&>);
+    static_assert( can_call_base_on<std::ranges::filter_view<NoCopyRange, Pred>&&>);
+    static_assert( can_call_base_on<std::ranges::filter_view<NoCopyRange, Pred>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/begin.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/begin.pass.cpp
new file mode 100644 (file)
index 0000000..1e0ee63
--- /dev/null
@@ -0,0 +1,201 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator begin();
+
+#include <ranges>
+
+#include <cassert>
+#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_;
+};
+
+// A range that isn't a forward_range, used to test filter_view
+// when we don't cache the result of begin()
+struct InputRange : std::ranges::view_base {
+  using Iterator = cpp17_input_iterator<int*>;
+  using Sentinel = sentinel_wrapper<Iterator>;
+  constexpr explicit InputRange(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 i) const { return i % 2 == 0; }
+};
+
+template <typename Range>
+constexpr void general_tests() {
+  int buff[] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+  // Check the return type of `.begin()`
+  {
+    Range range(buff, buff + 1);
+    auto pred = [](int) { return true; };
+    std::ranges::filter_view view(range, pred);
+    using FilterIterator = std::ranges::iterator_t<decltype(view)>;
+    ASSERT_SAME_TYPE(FilterIterator, decltype(view.begin()));
+  }
+
+  // begin() over an empty range
+  {
+    Range range(buff, buff);
+    auto pred = [](int) { return true; };
+    std::ranges::filter_view view(range, pred);
+    auto it = view.begin();
+    assert(base(it.base()) == buff);
+    assert(it == view.end());
+  }
+
+  // begin() over a 1-element range
+  {
+    {
+      Range range(buff, buff + 1);
+      auto pred = [](int i) { return i == 1; };
+      std::ranges::filter_view view(range, pred);
+      auto it = view.begin();
+      assert(base(it.base()) == buff);
+    }
+    {
+      Range range(buff, buff + 1);
+      auto pred = [](int) { return false; };
+      std::ranges::filter_view view(range, pred);
+      auto it = view.begin();
+      assert(base(it.base()) == buff + 1);
+      assert(it == view.end());
+    }
+  }
+
+  // begin() over a 2-element range
+  {
+    {
+      Range range(buff, buff + 2);
+      auto pred = [](int i) { return i == 1; };
+      std::ranges::filter_view view(range, pred);
+      auto it = view.begin();
+      assert(base(it.base()) == buff);
+    }
+    {
+      Range range(buff, buff + 2);
+      auto pred = [](int i) { return i == 2; };
+      std::ranges::filter_view view(range, pred);
+      auto it = view.begin();
+      assert(base(it.base()) == buff + 1);
+    }
+    {
+      Range range(buff, buff + 2);
+      auto pred = [](int) { return false; };
+      std::ranges::filter_view view(range, pred);
+      auto it = view.begin();
+      assert(base(it.base()) == buff + 2);
+      assert(it == view.end());
+    }
+  }
+
+  // begin() over a N-element range
+  {
+    for (int k = 1; k != 8; ++k) {
+      Range range(buff, buff + 8);
+      auto pred = [=](int i) { return i == k; };
+      std::ranges::filter_view view(range, pred);
+      auto it = view.begin();
+      assert(base(it.base()) == buff + (k - 1));
+    }
+    {
+      Range range(buff, buff + 8);
+      auto pred = [](int) { return false; };
+      std::ranges::filter_view view(range, pred);
+      auto it = view.begin();
+      assert(base(it.base()) == buff + 8);
+      assert(it == view.end());
+    }
+  }
+
+  // Make sure we do not make a copy of the predicate when we call begin()
+  // (we should be passing it to ranges::find_if using std::ref)
+  {
+    bool moved = false, copied = false;
+    Range range(buff, buff + 2);
+    std::ranges::filter_view view(range, TrackingPred(&moved, &copied));
+    moved = false;
+    copied = 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 i) mutable { return i % 2 == 0; };
+    std::ranges::filter_view view(range, pred);
+    auto it = view.begin();
+    assert(base(it.base()) == buff + 1);
+  }
+
+  // Test with a predicate that takes by non-const reference
+  {
+    Range range(buff, buff + 8);
+    auto pred = [](int& i) { return i % 2 == 0; };
+    std::ranges::filter_view view(range, pred);
+    auto it = view.begin();
+    assert(base(it.base()) == buff + 1);
+  }
+}
+
+template <typename ForwardRange>
+constexpr void cache_tests() {
+  int buff[] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+  // Make sure that we cache the result of begin() on subsequent calls
+  // (only applies to forward_ranges)
+  ForwardRange range(buff, buff + 8);
+  int called = 0;
+  auto pred = [&](int i) { ++called; return i == 3; };
+
+  std::ranges::filter_view view(range, pred);
+  assert(called == 0);
+  for (int k = 0; k != 3; ++k) {
+    auto it = view.begin();
+    assert(base(it.base()) == buff + 2);
+    assert(called == 3);
+  }
+}
+
+constexpr bool test() {
+  general_tests<Range>();
+  general_tests<InputRange>(); // test when we don't cache the result
+  cache_tests<Range>();
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/constraints.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/constraints.compile.pass.cpp
new file mode 100644 (file)
index 0000000..c00be1b
--- /dev/null
@@ -0,0 +1,110 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// Check constraints on the type itself.
+//
+// template<input_range View, indirect_unary_predicate<iterator_t<View>> Pred>
+//    requires view<View> && is_object_v<Pred>
+// class filter_view;
+
+#include <ranges>
+
+#include <concepts>
+#include <cstddef>
+#include <iterator>
+#include <type_traits>
+
+template <class View, class Pred>
+concept can_form_filter_view = requires {
+  typename std::ranges::filter_view<View, Pred>;
+};
+
+// filter_view is not valid when the view is not an input_range
+namespace test1 {
+  struct View : std::ranges::view_base {
+    struct NotInputIterator {
+      NotInputIterator& operator++();
+      void operator++(int);
+      int& operator*() const;
+      using difference_type = std::ptrdiff_t;
+      friend bool operator==(NotInputIterator const&, NotInputIterator const&);
+    };
+    NotInputIterator begin() const;
+    NotInputIterator end() const;
+  };
+  struct Pred { bool operator()(int) const; };
+
+  static_assert(!std::ranges::input_range<View>);
+  static_assert( std::indirect_unary_predicate<Pred, int*>);
+  static_assert( std::ranges::view<View>);
+  static_assert( std::is_object_v<Pred>);
+  static_assert(!can_form_filter_view<View, Pred>);
+}
+
+// filter_view is not valid when the predicate is not indirect_unary_predicate
+namespace test2 {
+  struct View : std::ranges::view_base {
+    int* begin() const;
+    int* end() const;
+  };
+  struct Pred { };
+
+  static_assert( std::ranges::input_range<View>);
+  static_assert(!std::indirect_unary_predicate<Pred, int*>);
+  static_assert( std::ranges::view<View>);
+  static_assert( std::is_object_v<Pred>);
+  static_assert(!can_form_filter_view<View, Pred>);
+}
+
+// filter_view is not valid when the view is not a view
+namespace test3 {
+  struct View {
+    int* begin() const;
+    int* end() const;
+  };
+  struct Pred { bool operator()(int) const; };
+
+  static_assert( std::ranges::input_range<View>);
+  static_assert( std::indirect_unary_predicate<Pred, int*>);
+  static_assert(!std::ranges::view<View>);
+  static_assert( std::is_object_v<Pred>);
+  static_assert(!can_form_filter_view<View, Pred>);
+}
+
+// filter_view is not valid when the predicate is not an object type
+namespace test4 {
+  struct View : std::ranges::view_base {
+    int* begin() const;
+    int* end() const;
+  };
+  using Pred = bool(&)(int);
+
+  static_assert( std::ranges::input_range<View>);
+  static_assert( std::indirect_unary_predicate<Pred, int*>);
+  static_assert( std::ranges::view<View>);
+  static_assert(!std::is_object_v<Pred>);
+  static_assert(!can_form_filter_view<View, Pred>);
+}
+
+// filter_view is valid when all the constraints are satisfied (test the test)
+namespace test5 {
+  struct View : std::ranges::view_base {
+    int* begin() const;
+    int* end() const;
+  };
+  struct Pred { bool operator()(int) const; };
+
+  static_assert( std::ranges::input_range<View>);
+  static_assert( std::indirect_unary_predicate<Pred, int*>);
+  static_assert( std::ranges::view<View>);
+  static_assert( std::is_object_v<Pred>);
+  static_assert( can_form_filter_view<View, Pred>);
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/ctad.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/ctad.pass.cpp
new file mode 100644 (file)
index 0000000..a07c03e
--- /dev/null
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// template <class Range, class Pred>
+// filter_view(Range&&, Pred) -> filter_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> && !std::ranges::view<Range>);
+
+struct Pred {
+  constexpr bool operator()(int i) const { return i % 2 == 0; }
+};
+
+constexpr bool test() {
+  {
+    View v;
+    Pred pred;
+    std::ranges::filter_view view(v, pred);
+    static_assert(std::is_same_v<decltype(view), std::ranges::filter_view<View, Pred>>);
+  }
+
+  // Test with a range that isn't a view, to make sure we properly use views::all_t in the implementation.
+  {
+    Range r;
+    Pred pred;
+    std::ranges::filter_view view(r, pred);
+    static_assert(std::is_same_v<decltype(view), std::ranges::filter_view<std::ranges::ref_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.filter/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/ctor.default.pass.cpp
new file mode 100644 (file)
index 0000000..6926166
--- /dev/null
@@ -0,0 +1,108 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// filter_view() requires std::default_initializable<View> &&
+//                        std::default_initializable<Pred> = default;
+
+#include <ranges>
+
+#include <cassert>
+#include <type_traits>
+
+constexpr int buff[] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+struct DefaultConstructibleView : std::ranges::view_base {
+  constexpr DefaultConstructibleView() : begin_(buff), end_(buff + 8) { }
+  constexpr int const* begin() const { return begin_; }
+  constexpr int const* end() const { return end_; }
+private:
+  int const* begin_;
+  int const* end_;
+};
+
+struct DefaultConstructiblePredicate {
+  DefaultConstructiblePredicate() = default;
+  constexpr bool operator()(int i) const { return i % 2 == 0; }
+};
+
+struct NoDefaultView : std::ranges::view_base {
+  NoDefaultView() = delete;
+  int* begin() const;
+  int* end() const;
+};
+
+struct NoDefaultPredicate {
+  NoDefaultPredicate() = delete;
+  constexpr bool operator()(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) const;
+};
+
+constexpr bool test() {
+  {
+    using View = std::ranges::filter_view<DefaultConstructibleView, DefaultConstructiblePredicate>;
+    View view;
+    auto it = view.begin(), end = view.end();
+    assert(*it++ == 2);
+    assert(*it++ == 4);
+    assert(*it++ == 6);
+    assert(*it++ == 8);
+    assert(it == end);
+  }
+
+  {
+    using View = std::ranges::filter_view<DefaultConstructibleView, DefaultConstructiblePredicate>;
+    View view = {};
+    auto it = view.begin(), end = view.end();
+    assert(*it++ == 2);
+    assert(*it++ == 4);
+    assert(*it++ == 6);
+    assert(*it++ == 8);
+    assert(it == end);
+  }
+
+  // Check cases where the default constructor isn't provided
+  {
+    static_assert(!std::is_default_constructible_v<std::ranges::filter_view<NoDefaultView,            DefaultConstructiblePredicate>>);
+    static_assert(!std::is_default_constructible_v<std::ranges::filter_view<DefaultConstructibleView, NoDefaultPredicate>>);
+    static_assert(!std::is_default_constructible_v<std::ranges::filter_view<NoDefaultView,            NoDefaultPredicate>>);
+  }
+
+  // Check noexcept-ness
+  {
+    {
+      using View = std::ranges::filter_view<DefaultConstructibleView, DefaultConstructiblePredicate>;
+      static_assert(!noexcept(View()));
+    }
+    {
+      using View = std::ranges::filter_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.filter/ctor.view_pred.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/ctor.view_pred.pass.cpp
new file mode 100644 (file)
index 0000000..04318d5
--- /dev/null
@@ -0,0 +1,102 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr filter_view(View, Pred);
+
+#include <ranges>
+
+#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_;
+};
+
+struct Pred {
+  constexpr bool operator()(int i) const { return i % 2 != 0; }
+};
+
+struct TrackingPred : TrackInitialization {
+  using TrackInitialization::TrackInitialization;
+  constexpr bool operator()(int) const;
+};
+
+struct TrackingRange : TrackInitialization, std::ranges::view_base {
+  using TrackInitialization::TrackInitialization;
+  int* begin() const;
+  int* end() const;
+};
+
+constexpr bool test() {
+  int buff[] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+  // Test explicit syntax
+  {
+    Range range(buff, buff + 8);
+    Pred pred;
+    std::ranges::filter_view<Range, Pred> view(range, pred);
+    auto it = view.begin(), end = view.end();
+    assert(*it++ == 1);
+    assert(*it++ == 3);
+    assert(*it++ == 5);
+    assert(*it++ == 7);
+    assert(it == end);
+  }
+
+  // Test implicit syntax
+  {
+    Range range(buff, buff + 8);
+    Pred pred;
+    std::ranges::filter_view<Range, Pred> view = {range, pred};
+    auto it = view.begin(), end = view.end();
+    assert(*it++ == 1);
+    assert(*it++ == 3);
+    assert(*it++ == 5);
+    assert(*it++ == 7);
+    assert(it == end);
+  }
+
+  // Make sure we move the view
+  {
+    bool moved = false, copied = false;
+    TrackingRange range(&moved, &copied);
+    Pred pred;
+    [[maybe_unused]] std::ranges::filter_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 + 8);
+    TrackingPred pred(&moved, &copied);
+    [[maybe_unused]] std::ranges::filter_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.filter/end.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/end.pass.cpp
new file mode 100644 (file)
index 0000000..41f467b
--- /dev/null
@@ -0,0 +1,115 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr auto end();
+
+#include <ranges>
+
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include "test_iterators.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 CommonRange : std::ranges::view_base {
+  using Iterator = forward_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_;
+};
+
+constexpr bool test() {
+  int buff[] = {1, 2, 3, 4, 5, 6, 7, 8};
+
+  // Check the return type of `.end()`
+  {
+    Range range(buff, buff + 1);
+    auto pred = [](int) { return true; };
+    std::ranges::filter_view view(range, pred);
+    using FilterSentinel = std::ranges::sentinel_t<decltype(view)>;
+    ASSERT_SAME_TYPE(FilterSentinel, decltype(view.end()));
+  }
+
+  // end() on an empty range
+  {
+    Range range(buff, buff);
+    auto pred = [](int) { return true; };
+    std::ranges::filter_view view(range, pred);
+    auto end = view.end();
+    assert(base(base(end.base())) == buff);
+  }
+
+  // end() on a 1-element range
+  {
+    Range range(buff, buff + 1);
+    auto pred = [](int) { return true; };
+    std::ranges::filter_view view(range, pred);
+    auto end = view.end();
+    assert(base(base(end.base())) == buff + 1);
+    static_assert(!std::is_same_v<decltype(end), decltype(view.begin())>);
+  }
+
+  // end() on a 2-element range
+  {
+    Range range(buff, buff + 2);
+    auto pred = [](int) { return true; };
+    std::ranges::filter_view view(range, pred);
+    auto end = view.end();
+    assert(base(base(end.base())) == buff + 2);
+    static_assert(!std::is_same_v<decltype(end), decltype(view.begin())>);
+  }
+
+  // end() on a N-element range
+  {
+    for (int k = 1; k != 8; ++k) {
+      Range range(buff, buff + 8);
+      auto pred = [=](int i) { return i == k; };
+      std::ranges::filter_view view(range, pred);
+      auto end = view.end();
+      assert(base(base(end.base())) == buff + 8);
+      static_assert(!std::is_same_v<decltype(end), decltype(view.begin())>);
+    }
+  }
+
+  // end() on a common_range
+  {
+    CommonRange range(buff, buff + 8);
+    auto pred = [](int i) { return i % 2 == 0; };
+    std::ranges::filter_view view(range, pred);
+    auto end = view.end();
+    assert(base(end.base()) == buff + 8);
+    static_assert(std::is_same_v<decltype(end), decltype(view.begin())>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/arrow.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/arrow.pass.cpp
new file mode 100644 (file)
index 0000000..a1c2871
--- /dev/null
@@ -0,0 +1,137 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator_t<V> operator->() const
+//    requires has-arrow<iterator_t<V>> && copyable<iterator_t<V>>
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <cstddef>
+#include <utility>
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+struct Point {
+  int x;
+  int y;
+};
+
+template <class T>
+concept has_arrow = requires (T t) {
+  { t->x };
+};
+static_assert(has_arrow<Point*>); // test the test
+
+struct WithArrowOperator {
+  using iterator_category = std::input_iterator_tag;
+  using difference_type = std::ptrdiff_t;
+  using value_type = Point;
+
+  constexpr explicit WithArrowOperator(Point* p) : p_(p) { }
+  constexpr Point& operator*() const { return *p_; }
+  constexpr Point* operator->() const { return p_; } // has arrow
+  constexpr WithArrowOperator& operator++() { ++p_; return *this; }
+  constexpr WithArrowOperator operator++(int) { return WithArrowOperator(p_++); }
+
+  friend constexpr Point* base(WithArrowOperator const& i) { return i.p_; }
+  Point* p_;
+};
+static_assert(std::input_iterator<WithArrowOperator>);
+
+struct WithNonCopyableIterator : std::ranges::view_base {
+  struct iterator {
+    using iterator_category = std::input_iterator_tag;
+    using difference_type = std::ptrdiff_t;
+    using value_type = Point;
+
+    iterator(iterator const&) = delete; // not copyable
+    iterator(iterator&&);
+    iterator& operator=(iterator&&);
+    Point& operator*() const;
+    iterator operator->() const;
+    iterator& operator++();
+    iterator operator++(int);
+
+    // We need this to use Point* as a sentinel type below. sentinel_wrapper
+    // can't be used because this iterator is not copyable.
+    friend bool operator==(iterator const&, Point*);
+  };
+
+  iterator begin() const;
+  Point* end() const;
+};
+static_assert(std::ranges::input_range<WithNonCopyableIterator>);
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  std::array<Point, 5> array{{{0, 0}, {1, 1}, {2, 2}, {3, 3}, {4, 4}}};
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+  using FilterIterator = std::ranges::iterator_t<FilterView>;
+
+  auto make_filter_view = [](auto begin, auto end, auto pred) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return FilterView(std::move(view), pred);
+  };
+
+  for (std::ptrdiff_t n = 0; n != 5; ++n) {
+    FilterView view = make_filter_view(array.begin(), array.end(), AlwaysTrue{});
+    FilterIterator const iter(view, Iterator(array.begin() + n));
+    std::same_as<Iterator> decltype(auto) result = iter.operator->();
+    assert(base(result) == array.begin() + n);
+    assert(iter->x == n);
+    assert(iter->y == n);
+  }
+}
+
+constexpr bool tests() {
+  test<WithArrowOperator>();
+  test<Point*>();
+  test<Point const*>();
+  test<contiguous_iterator<Point*>>();
+  test<contiguous_iterator<Point const*>>();
+
+  // Make sure filter_view::iterator doesn't have operator-> if the
+  // underlying iterator doesn't have one.
+  {
+    auto check_no_arrow = []<class It> {
+      using View = minimal_view<It, sentinel_wrapper<It>>;
+      using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+      using FilterIterator = std::ranges::iterator_t<FilterView>;
+      static_assert(!has_arrow<FilterIterator>);
+    };
+    check_no_arrow.operator()<cpp17_input_iterator<Point*>>();
+    check_no_arrow.operator()<cpp20_input_iterator<Point*>>();
+    check_no_arrow.operator()<forward_iterator<Point*>>();
+    check_no_arrow.operator()<bidirectional_iterator<Point*>>();
+    check_no_arrow.operator()<random_access_iterator<Point*>>();
+    check_no_arrow.operator()<int*>();
+  }
+
+  // Make sure filter_view::iterator doesn't have operator-> if the
+  // underlying iterator is not copyable.
+  {
+    using FilterView = std::ranges::filter_view<WithNonCopyableIterator, AlwaysTrue>;
+    using FilterIterator = std::ranges::iterator_t<FilterView>;
+    static_assert(!has_arrow<FilterIterator>);
+  }
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/base.pass.cpp
new file mode 100644 (file)
index 0000000..8727040
--- /dev/null
@@ -0,0 +1,73 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator_t<V> const& base() const& noexcept;
+// constexpr iterator_t<V> base() &&;
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <utility>
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+  using FilterIterator = std::ranges::iterator_t<FilterView>;
+
+  auto make_filter_view = [](auto begin, auto end, auto pred) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return FilterView(std::move(view), pred);
+  };
+
+  std::array<int, 5> array{0, 1, 2, 3, 4};
+  FilterView view = make_filter_view(array.begin(), array.end(), AlwaysTrue{});
+
+  // Test the const& version
+  {
+    FilterIterator const iter(view, Iterator(array.begin()));
+    Iterator const& result = iter.base();
+    ASSERT_SAME_TYPE(Iterator const&, decltype(iter.base()));
+    ASSERT_NOEXCEPT(iter.base());
+    assert(base(result) == array.begin());
+  }
+
+  // Test the && version
+  {
+    FilterIterator iter(view, Iterator(array.begin()));
+    Iterator result = std::move(iter).base();
+    ASSERT_SAME_TYPE(Iterator, decltype(std::move(iter).base()));
+    assert(base(result) == array.begin());
+  }
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+  test<int const*>();
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/compare.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/compare.pass.cpp
new file mode 100644 (file)
index 0000000..e3d85bd
--- /dev/null
@@ -0,0 +1,90 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// friend constexpr bool operator==(iterator const&, iterator const&)
+//  requires equality_comparable<iterator_t<V>>
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <utility>
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+template <class T>
+concept has_equal = requires (T const& x, T const& y) { { x == y }; };
+
+template <class Iterator>
+constexpr void test() {
+  using Sentinel = sentinel_wrapper<Iterator>;
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+  using FilterIterator = std::ranges::iterator_t<FilterView>;
+
+  auto make_filter_view = [](auto begin, auto end, auto pred) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return FilterView(std::move(view), pred);
+  };
+
+  {
+    std::array<int, 5> array{0, 1, 2, 3, 4};
+    FilterView view = make_filter_view(array.begin(), array.end(), AlwaysTrue{});
+    FilterIterator it1 = view.begin();
+    FilterIterator it2 = view.begin();
+    std::same_as<bool> decltype(auto) result = (it1 == it2);
+    assert(result);
+
+    ++it1;
+    assert(!(it1 == it2));
+  }
+
+  {
+    std::array<int, 5> array{0, 1, 2, 3, 4};
+    FilterView view = make_filter_view(array.begin(), array.end(), AlwaysTrue{});
+    assert(!(view.begin() == view.end()));
+  }
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+
+  test<cpp17_input_iterator<int const*>>();
+  test<forward_iterator<int const*>>();
+  test<bidirectional_iterator<int const*>>();
+  test<random_access_iterator<int const*>>();
+  test<contiguous_iterator<int const*>>();
+  test<int const*>();
+
+  // Make sure `operator==` isn't provided for non comparable iterators
+  {
+    using Iterator = cpp20_input_iterator<int*>;
+    using Sentinel = sentinel_wrapper<Iterator>;
+    using FilterView = std::ranges::filter_view<minimal_view<Iterator, Sentinel>, AlwaysTrue>;
+    using FilterIterator = std::ranges::iterator_t<FilterView>;
+    static_assert(!has_equal<FilterIterator>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/ctor.default.pass.cpp
new file mode 100644 (file)
index 0000000..8b25f6c
--- /dev/null
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// std::ranges::filter_view<V>::<iterator>() requires default_initializable<iterator_t<V>> = default;
+
+#include <ranges>
+
+#include <cassert>
+#include <type_traits>
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, bool IsNoexcept>
+constexpr void test_default_constructible() {
+  // Make sure the iterator is default constructible when the underlying iterator is.
+  using View = minimal_view<Iterator, sentinel_wrapper<Iterator>>;
+  using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+  using FilterIterator = std::ranges::iterator_t<FilterView>;
+  FilterIterator iter1{};
+  FilterIterator iter2;
+  assert(iter1 == iter2);
+  static_assert(noexcept(FilterIterator()) == IsNoexcept);
+}
+
+template <class Iterator>
+constexpr void test_not_default_constructible() {
+  // Make sure the iterator is *not* default constructible when the underlying iterator isn't.
+  using View = minimal_view<Iterator, sentinel_wrapper<Iterator>>;
+  using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+  using FilterIterator = std::ranges::iterator_t<FilterView>;
+  static_assert(!std::is_default_constructible_v<FilterIterator>);
+}
+
+constexpr bool tests() {
+  test_not_default_constructible<cpp17_input_iterator<int*>>();
+  test_not_default_constructible<cpp20_input_iterator<int*>>();
+  test_default_constructible<forward_iterator<int*>,         /* noexcept */ false>();
+  test_default_constructible<bidirectional_iterator<int*>,   /* noexcept */ false>();
+  test_default_constructible<random_access_iterator<int*>,   /* noexcept */ false>();
+  test_default_constructible<contiguous_iterator<int*>,      /* noexcept */ false>();
+  test_default_constructible<int*,                           /* noexcept */ true>();
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/ctor.parent_iter.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/ctor.parent_iter.pass.cpp
new file mode 100644 (file)
index 0000000..c883714
--- /dev/null
@@ -0,0 +1,52 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr std::ranges::filter_view::<iterator>(filter_view&, iterator_t<V>);
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <utility>
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+  using FilterIterator = std::ranges::iterator_t<FilterView>;
+
+  std::array<int, 10> array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+  View view(Iterator(array.begin()), Sentinel(Iterator(array.end())));
+  Iterator iter = view.begin();
+
+  FilterView filter_view(std::move(view), AlwaysTrue{});
+  FilterIterator filter_iter(filter_view, std::move(iter));
+  assert(base(filter_iter.base()) == array.begin());
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/decrement.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/decrement.pass.cpp
new file mode 100644 (file)
index 0000000..25339d1
--- /dev/null
@@ -0,0 +1,143 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator& operator--() requires bidirectional_range<V>;
+// constexpr iterator operator--(int) requires bidirectional_range<V>;
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <iterator>
+#include <utility>
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+struct EqualTo {
+  int x;
+  constexpr bool operator()(int y) const { return x == y; }
+};
+
+template <class T>
+concept has_pre_decrement = requires (T t) { { --t }; };
+
+template <class T>
+concept has_post_decrement = requires (T t) { { t-- }; };
+
+template <class Iterator>
+using FilterIteratorFor = std::ranges::iterator_t<
+  std::ranges::filter_view<minimal_view<Iterator, sentinel_wrapper<Iterator>>, EqualTo>
+>;
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, EqualTo>;
+  using FilterIterator = std::ranges::iterator_t<FilterView>;
+
+  auto make_filter_view = [](auto begin, auto end, auto pred) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return FilterView(std::move(view), pred);
+  };
+
+  // Test with a single satisfied value
+  {
+    std::array<int, 5> array{0, 1, 2, 3, 4};
+    FilterView view = make_filter_view(array.begin(), array.end(), EqualTo{1});
+    FilterIterator it = std::ranges::next(view.begin(), view.end());
+    assert(base(it.base()) == array.end()); // test the test
+
+    FilterIterator& result = --it;
+    ASSERT_SAME_TYPE(FilterIterator&, decltype(--it));
+    assert(&result == &it);
+    assert(base(result.base()) == array.begin() + 1);
+  }
+
+  // Test with more than one satisfied value
+  {
+    std::array<int, 6> array{0, 1, 2, 3, 1, 4};
+    FilterView view = make_filter_view(array.begin(), array.end(), EqualTo{1});
+    FilterIterator it = std::ranges::next(view.begin(), view.end());
+    assert(base(it.base()) == array.end()); // test the test
+
+    FilterIterator& result = --it;
+    assert(&result == &it);
+    assert(base(result.base()) == array.begin() + 4);
+
+    --it;
+    assert(base(it.base()) == array.begin() + 1);
+  }
+
+  // Test going forward and then backward on the same iterator
+  {
+    std::array<int, 10> array{0, 1, 2, 3, 1, 1, 4, 5, 1, 6};
+    FilterView view = make_filter_view(array.begin(), array.end(), EqualTo{1});
+    FilterIterator it = view.begin();
+    ++it;
+    --it; assert(base(it.base()) == array.begin() + 1);
+    ++it; ++it;
+    --it; assert(base(it.base()) == array.begin() + 4);
+    ++it; ++it;
+    --it; assert(base(it.base()) == array.begin() + 5);
+    ++it; ++it;
+    --it; assert(base(it.base()) == array.begin() + 8);
+  }
+
+  // Test post-decrement
+  {
+    std::array<int, 6> array{0, 1, 2, 3, 1, 4};
+    FilterView view = make_filter_view(array.begin(), array.end(), EqualTo{1});
+    FilterIterator it = std::ranges::next(view.begin(), view.end());
+    assert(base(it.base()) == array.end()); // test the test
+
+    FilterIterator result = it--;
+    ASSERT_SAME_TYPE(FilterIterator, decltype(it--));
+    assert(base(result.base()) == array.end());
+    assert(base(it.base()) == array.begin() + 4);
+
+    result = it--;
+    assert(base(result.base()) == array.begin() + 4);
+    assert(base(it.base()) == array.begin() + 1);
+  }
+}
+
+constexpr bool tests() {
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+
+  test<bidirectional_iterator<int const*>>();
+  test<random_access_iterator<int const*>>();
+  test<contiguous_iterator<int const*>>();
+  test<int const*>();
+
+  // Make sure `operator--` isn't provided for non bidirectional ranges
+  {
+    static_assert(!has_pre_decrement<FilterIteratorFor<cpp17_input_iterator<int*>>>);
+    static_assert(!has_pre_decrement<FilterIteratorFor<cpp20_input_iterator<int*>>>);
+    static_assert(!has_pre_decrement<FilterIteratorFor<forward_iterator<int*>>>);
+
+    static_assert(!has_post_decrement<FilterIteratorFor<cpp17_input_iterator<int*>>>);
+    static_assert(!has_post_decrement<FilterIteratorFor<cpp20_input_iterator<int*>>>);
+    static_assert(!has_post_decrement<FilterIteratorFor<forward_iterator<int*>>>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/deref.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/deref.pass.cpp
new file mode 100644 (file)
index 0000000..6e3192f
--- /dev/null
@@ -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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr range_reference_t<V> operator*() const;
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <cstddef>
+#include <utility>
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+template <class Iterator, class ValueType = int, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+  using FilterIterator = std::ranges::iterator_t<FilterView>;
+
+  auto make_filter_view = [](auto begin, auto end, auto pred) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return FilterView(std::move(view), pred);
+  };
+
+  std::array array{0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
+  FilterView view = make_filter_view(array.begin(), array.end(), AlwaysTrue{});
+
+  for (std::size_t n = 0; n != array.size(); ++n) {
+    FilterIterator const iter(view, Iterator(array.begin() + n));
+    ValueType& result = *iter;
+    ASSERT_SAME_TYPE(ValueType&, decltype(*iter));
+    assert(&result == array.begin() + n);
+  }
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+
+  test<cpp17_input_iterator<int const*>,   int const>();
+  test<cpp20_input_iterator<int const*>,   int const>();
+  test<forward_iterator<int const*>,       int const>();
+  test<bidirectional_iterator<int const*>, int const>();
+  test<random_access_iterator<int const*>, int const>();
+  test<contiguous_iterator<int const*>,    int const>();
+  test<int const*,                         int const>();
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/increment.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/increment.pass.cpp
new file mode 100644 (file)
index 0000000..42d0abd
--- /dev/null
@@ -0,0 +1,184 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr iterator& operator++();
+// constexpr void operator++(int);
+// constexpr iterator operator++(int) requires forward_range<V>;
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <type_traits>
+#include <utility>
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+struct EqualTo {
+  int x;
+  constexpr bool operator()(int y) const { return x == y; }
+};
+
+struct TrackingPred : TrackInitialization {
+  using TrackInitialization::TrackInitialization;
+  constexpr bool operator()(int i) const { return i == 1; }
+};
+
+template <class Iterator, bool IsForwardRange, bool IsConst>
+constexpr void test() {
+  using Sentinel = sentinel_wrapper<Iterator>;
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, EqualTo>;
+  using FilterIterator = std::ranges::iterator_t<FilterView>;
+
+  auto make_filter_view = [](auto begin, auto end, auto pred) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return FilterView(std::move(view), pred);
+  };
+
+  // Increment an iterator when it won't find another satisfied value after begin()
+  {
+    std::array<int, 5> array{0, 1, 2, 3, 4};
+    FilterView view = make_filter_view(array.begin(), array.end(), EqualTo{1});
+
+    FilterIterator it = view.begin();
+    FilterIterator& result = ++it;
+    ASSERT_SAME_TYPE(FilterIterator&, decltype(++it));
+    assert(&result == &it);
+    assert(base(result.base()) == array.end());
+  }
+
+  // Increment the iterator and it finds another value after begin()
+  {
+    std::array<int, 5> array{99, 1, 99, 1, 99};
+    FilterView view = make_filter_view(array.begin(), array.end(), EqualTo{1});
+
+    FilterIterator it = view.begin();
+    ++it;
+    assert(base(it.base()) == array.begin() + 3);
+  }
+
+  // Increment advances all the way to the end of the range
+  {
+    std::array<int, 5> array{99, 1, 99, 99, 1};
+    FilterView view = make_filter_view(array.begin(), array.end(), EqualTo{1});
+
+    FilterIterator it = view.begin();
+    ++it;
+    assert(base(it.base()) == array.begin() + 4);
+  }
+
+  // Increment an iterator multiple times
+  {
+    std::array<int, 10> array{0, 1, 2, 3, 1, 1, 4, 5, 1, 6};
+    FilterView view = make_filter_view(array.begin(), array.end(), EqualTo{1});
+
+    FilterIterator it = view.begin();
+          assert(base(it.base()) == array.begin() + 1);
+    ++it; assert(base(it.base()) == array.begin() + 4);
+    ++it; assert(base(it.base()) == array.begin() + 5);
+    ++it; assert(base(it.base()) == array.begin() + 8);
+    ++it; assert(base(it.base()) == array.end());
+  }
+
+  // Test with a predicate that takes by non-const reference
+  if constexpr (!IsConst) {
+    std::array<int, 4> array{99, 1, 99, 1};
+    View v{Iterator(array.begin()), Sentinel(Iterator(array.end()))};
+    auto pred = [](int& x) { return x == 1; };
+    auto view = std::ranges::filter_view(std::move(v), pred);
+    auto it = view.begin();
+    assert(base(it.base()) == array.begin() + 1);
+    ++it;
+    assert(base(it.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::find_if using std::ref)
+  {
+    bool moved = false, copied = false;
+    std::array<int, 3> array{1, 1, 1};
+    View v{Iterator(array.begin()), Sentinel(Iterator(array.end()))};
+    auto view = std::ranges::filter_view(std::move(v), TrackingPred(&moved, &copied));
+    moved = false;
+    copied = false;
+    auto it = view.begin();
+    ++it;
+    it++;
+    assert(!moved);
+    assert(!copied);
+  }
+
+  // Check post-increment for input ranges
+  if constexpr (!IsForwardRange) {
+    std::array<int, 10> array{0, 1, 2, 3, 1, 1, 4, 5, 1, 6};
+    FilterView view = make_filter_view(array.begin(), array.end(), EqualTo{1});
+
+    FilterIterator it = view.begin();
+          assert(base(it.base()) == array.begin() + 1);
+    it++; assert(base(it.base()) == array.begin() + 4);
+    it++; assert(base(it.base()) == array.begin() + 5);
+    it++; assert(base(it.base()) == array.begin() + 8);
+    it++; assert(base(it.base()) == array.end());
+    static_assert(std::is_same_v<decltype(it++), void>);
+  }
+
+  // Check post-increment for forward ranges
+  if constexpr (IsForwardRange) {
+    std::array<int, 10> array{0, 1, 2, 3, 1, 1, 4, 5, 1, 6};
+    FilterView view = make_filter_view(array.begin(), array.end(), EqualTo{1});
+
+    FilterIterator it = view.begin();
+    FilterIterator result = it++;
+    ASSERT_SAME_TYPE(FilterIterator, decltype(it++));
+    assert(base(result.base()) == array.begin() + 1);
+    assert(base(it.base()) == array.begin() + 4);
+
+    result = it++;
+    assert(base(result.base()) == array.begin() + 4);
+    assert(base(it.base()) == array.begin() + 5);
+
+    result = it++;
+    assert(base(result.base()) == array.begin() + 5);
+    assert(base(it.base()) == array.begin() + 8);
+
+    result = it++;
+    assert(base(result.base()) == array.begin() + 8);
+    assert(base(it.base()) == array.end());
+  }
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>,   /* IsForwardRange */ false, /* IsConst */ false>();
+  test<cpp20_input_iterator<int*>,   /* IsForwardRange */ false, /* IsConst */ false>();
+  test<forward_iterator<int*>,       /* IsForwardRange */ true,  /* IsConst */ false>();
+  test<bidirectional_iterator<int*>, /* IsForwardRange */ true,  /* IsConst */ false>();
+  test<random_access_iterator<int*>, /* IsForwardRange */ true,  /* IsConst */ false>();
+  test<contiguous_iterator<int*>,    /* IsForwardRange */ true,  /* IsConst */ false>();
+  test<int*,                         /* IsForwardRange */ true,  /* IsConst */ false>();
+
+  test<cpp17_input_iterator<int const*>,   /* IsForwardRange */ false, /* IsConst */ true>();
+  test<cpp20_input_iterator<int const*>,   /* IsForwardRange */ false, /* IsConst */ true>();
+  test<forward_iterator<int const*>,       /* IsForwardRange */ true,  /* IsConst */ true>();
+  test<bidirectional_iterator<int const*>, /* IsForwardRange */ true,  /* IsConst */ true>();
+  test<random_access_iterator<int const*>, /* IsForwardRange */ true,  /* IsConst */ true>();
+  test<contiguous_iterator<int const*>,    /* IsForwardRange */ true,  /* IsConst */ true>();
+  test<int const*,                         /* IsForwardRange */ true,  /* IsConst */ true>();
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/iter_move.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/iter_move.pass.cpp
new file mode 100644 (file)
index 0000000..8ed05de
--- /dev/null
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// friend constexpr range_rvalue_reference_t<V> iter_move(iterator const& i)
+//  noexcept(noexcept(ranges::iter_move(i.current_)));
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <utility>
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+template <class Iterator, bool HasNoexceptIterMove>
+constexpr void test() {
+  using Sentinel = sentinel_wrapper<Iterator>;
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+  using FilterIterator = std::ranges::iterator_t<FilterView>;
+
+  auto make_filter_view = [](auto begin, auto end, auto pred) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return FilterView(std::move(view), pred);
+  };
+
+  {
+    std::array<int, 5> array{0, 1, 2, 3, 4};
+    FilterView view = make_filter_view(array.begin(), array.end(), AlwaysTrue{});
+    FilterIterator const it = view.begin();
+
+    int&& result = iter_move(it);
+    static_assert(noexcept(iter_move(it)) == HasNoexceptIterMove);
+    assert(&result == array.begin());
+  }
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>,           /* noexcept */ false>();
+  test<cpp20_input_iterator<int*>,           /* noexcept */ false>();
+  test<forward_iterator<int*>,               /* noexcept */ false>();
+  test<bidirectional_iterator<int*>,         /* noexcept */ false>();
+  test<random_access_iterator<int*>,         /* noexcept */ false>();
+  test<contiguous_iterator<int*>,            /* noexcept */ false>();
+  test<int*,                                 /* noexcept */ true>();
+  test<NoexceptIterMoveInputIterator<true>,  /* noexcept */ true>();
+  test<NoexceptIterMoveInputIterator<false>, /* noexcept */ false>();
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/iter_swap.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/iter_swap.pass.cpp
new file mode 100644 (file)
index 0000000..10e6b95
--- /dev/null
@@ -0,0 +1,91 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// friend constexpr void iter_swap(iterator const& x, iterator const& y)
+//  noexcept(noexcept(ranges::iter_swap(x.current_, y.current_)))
+//  requires(indirectly_swappable<iterator_t<V>>);
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <iterator>
+#include <utility>
+#include "test_iterators.h"
+#include "test_macros.h"
+#include "../types.h"
+
+template <class It>
+concept has_iter_swap = requires (It it) {
+  std::ranges::iter_swap(it, it);
+};
+
+struct IsEven {
+  constexpr bool operator()(int x) const { return x % 2 == 0; }
+};
+
+template <class Iterator, bool IsNoexcept>
+constexpr void test() {
+  using Sentinel = sentinel_wrapper<Iterator>;
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, IsEven>;
+  using FilterIterator = std::ranges::iterator_t<FilterView>;
+
+  auto make_filter_view = [](auto begin, auto end, auto pred) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return FilterView(std::move(view), pred);
+  };
+
+  {
+    std::array<int, 5> array{1, 2, 1, 4, 1};
+    FilterView view = make_filter_view(array.begin(), array.end(), IsEven{});
+    FilterIterator const it1 = view.begin();
+    FilterIterator const it2 = std::ranges::next(view.begin());
+
+    static_assert(std::is_same_v<decltype(iter_swap(it1, it2)), void>);
+    static_assert(noexcept(iter_swap(it1, it2)) == IsNoexcept);
+
+    assert(*it1 == 2 && *it2 == 4); // test the test
+    iter_swap(it1, it2);
+    assert(*it1 == 4);
+    assert(*it2 == 2);
+  }
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>,           /* noexcept */ false>();
+  test<cpp20_input_iterator<int*>,           /* noexcept */ false>();
+  test<forward_iterator<int*>,               /* noexcept */ false>();
+  test<bidirectional_iterator<int*>,         /* noexcept */ false>();
+  test<random_access_iterator<int*>,         /* noexcept */ false>();
+  test<contiguous_iterator<int*>,            /* noexcept */ false>();
+  test<int*,                                 /* noexcept */ true>();
+  test<NoexceptIterSwapInputIterator<true>,  /* noexcept */ true>();
+  test<NoexceptIterSwapInputIterator<false>, /* noexcept */ false>();
+
+  // Test that iter_swap requires the underlying iterator to be iter_swappable
+  {
+    using Iterator = int const*;
+    using View = minimal_view<Iterator, Iterator>;
+    using FilterView = std::ranges::filter_view<View, IsEven>;
+    using FilterIterator = std::ranges::iterator_t<FilterView>;
+    static_assert(!std::indirectly_swappable<Iterator>);
+    static_assert(!has_iter_swap<FilterIterator>);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/types.compile.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/iterator/types.compile.pass.cpp
new file mode 100644 (file)
index 0000000..831cedb
--- /dev/null
@@ -0,0 +1,91 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// std::filter_view::<iterator>::difference_type
+// std::filter_view::<iterator>::value_type
+// std::filter_view::<iterator>::iterator_category
+// std::filter_view::<iterator>::iterator_concept
+
+#include <ranges>
+
+#include <type_traits>
+#include "test_iterators.h"
+#include "../types.h"
+
+template <typename T>
+concept HasIteratorCategory = requires {
+  typename T::iterator_category;
+};
+
+template <class Iterator>
+using FilterViewFor = std::ranges::filter_view<
+  minimal_view<Iterator, sentinel_wrapper<Iterator>>,
+  AlwaysTrue
+>;
+
+template <class Iterator>
+using FilterIteratorFor = std::ranges::iterator_t<FilterViewFor<Iterator>>;
+
+struct ForwardIteratorWithInputCategory {
+  using difference_type = int;
+  using value_type = int;
+  using iterator_category = std::input_iterator_tag;
+  using iterator_concept = std::forward_iterator_tag;
+  ForwardIteratorWithInputCategory();
+  ForwardIteratorWithInputCategory& operator++();
+  ForwardIteratorWithInputCategory operator++(int);
+  int& operator*() const;
+  friend bool operator==(ForwardIteratorWithInputCategory, ForwardIteratorWithInputCategory);
+};
+static_assert(std::forward_iterator<ForwardIteratorWithInputCategory>);
+
+void f() {
+  // Check that value_type is range_value_t and difference_type is range_difference_t
+  {
+    auto test = []<class Iterator> {
+      using FilterView = FilterViewFor<Iterator>;
+      using FilterIterator = FilterIteratorFor<Iterator>;
+      static_assert(std::is_same_v<typename FilterIterator::value_type, std::ranges::range_value_t<FilterView>>);
+      static_assert(std::is_same_v<typename FilterIterator::difference_type, std::ranges::range_difference_t<FilterView>>);
+    };
+    test.operator()<cpp17_input_iterator<int*>>();
+    test.operator()<cpp20_input_iterator<int*>>();
+    test.operator()<forward_iterator<int*>>();
+    test.operator()<bidirectional_iterator<int*>>();
+    test.operator()<random_access_iterator<int*>>();
+    test.operator()<contiguous_iterator<int*>>();
+    test.operator()<int*>();
+  }
+
+  // Check iterator_concept for various categories of ranges
+  {
+    static_assert(std::is_same_v<FilterIteratorFor<cpp17_input_iterator<int*>>::iterator_concept, std::input_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<cpp20_input_iterator<int*>>::iterator_concept, std::input_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<ForwardIteratorWithInputCategory>::iterator_concept, std::forward_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<forward_iterator<int*>>::iterator_concept, std::forward_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<bidirectional_iterator<int*>>::iterator_concept, std::bidirectional_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<random_access_iterator<int*>>::iterator_concept, std::bidirectional_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<contiguous_iterator<int*>>::iterator_concept, std::bidirectional_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<int*>::iterator_concept, std::bidirectional_iterator_tag>);
+  }
+
+  // Check iterator_category for various categories of ranges
+  {
+    static_assert(!HasIteratorCategory<FilterIteratorFor<cpp17_input_iterator<int*>>>);
+    static_assert(!HasIteratorCategory<FilterIteratorFor<cpp20_input_iterator<int*>>>);
+    static_assert(std::is_same_v<FilterIteratorFor<ForwardIteratorWithInputCategory>::iterator_category, std::input_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<forward_iterator<int*>>::iterator_category, std::forward_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<bidirectional_iterator<int*>>::iterator_category, std::bidirectional_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<random_access_iterator<int*>>::iterator_category, std::bidirectional_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<contiguous_iterator<int*>>::iterator_category, std::bidirectional_iterator_tag>);
+    static_assert(std::is_same_v<FilterIteratorFor<int*>::iterator_category, std::bidirectional_iterator_tag>);
+  }
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/pred.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/pred.pass.cpp
new file mode 100644 (file)
index 0000000..39ce5b7
--- /dev/null
@@ -0,0 +1,65 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// Older Clangs don't properly deduce decltype(auto) with a concept constraint
+// XFAIL: clang-11, clang-12
+// XFAIL: apple-clang-12, apple-clang-13.0
+
+// 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) const;
+  int value;
+};
+
+constexpr bool test() {
+  {
+    Pred pred{42};
+    std::ranges::filter_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
+    std::same_as<Pred const&> decltype(auto) result2 = view.pred();
+    assert(&result == &result2);
+  }
+
+  // Same, but calling on a non-const view
+  {
+    Pred pred{42};
+    std::ranges::filter_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
+    std::same_as<Pred const&> decltype(auto) result2 = view.pred();
+    assert(&result == &result2);
+  }
+
+  return true;
+}
+
+int main(int, char**) {
+  test();
+  static_assert(test());
+
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/base.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/base.pass.cpp
new file mode 100644 (file)
index 0000000..c9ce33b
--- /dev/null
@@ -0,0 +1,57 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr sentinel_t<V> base() const;
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <utility>
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+  using FilterSentinel = std::ranges::sentinel_t<FilterView>;
+
+  auto make_filter_view = [](auto begin, auto end, auto pred) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return FilterView(std::move(view), pred);
+  };
+
+  std::array<int, 5> array{0, 1, 2, 3, 4};
+  FilterView view = make_filter_view(array.begin(), array.end(), AlwaysTrue{});
+
+  FilterSentinel const sent = view.end();
+  std::same_as<Sentinel> decltype(auto) result = sent.base();
+  assert(base(base(result)) == array.end());
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/compare.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/compare.pass.cpp
new file mode 100644 (file)
index 0000000..85ebea0
--- /dev/null
@@ -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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// friend constexpr bool operator==(iterator const&, sentinel const&);
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <concepts>
+#include <utility>
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View = minimal_view<Iterator, Sentinel>;
+
+  std::array<int, 5> array{0, 1, 2, 3, 4};
+
+  {
+    View v(Iterator(array.begin()), Sentinel(Iterator(array.end())));
+    std::ranges::filter_view view(std::move(v), AlwaysTrue{});
+    auto const it = view.begin();
+    auto const sent = view.end();
+    std::same_as<bool> decltype(auto) result = (it == sent);
+    assert(!result);
+  }
+  {
+    View v(Iterator(array.begin()), Sentinel(Iterator(array.end())));
+    std::ranges::filter_view view(std::move(v), [](auto) { return false; });
+    auto const it = view.begin();
+    auto const sent = view.end();
+    std::same_as<bool> decltype(auto) result = (it == sent);
+    assert(result);
+  }
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/ctor.default.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/ctor.default.pass.cpp
new file mode 100644 (file)
index 0000000..bc4a191
--- /dev/null
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// filter_view<V>::<sentinel>() = default;
+
+#include <ranges>
+
+#include <cassert>
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+  using FilterSentinel = std::ranges::sentinel_t<FilterView>;
+  FilterSentinel sent1{};
+  FilterSentinel sent2;
+  assert(base(base(sent1.base())) == base(base(sent2.base())));
+  static_assert(noexcept(FilterSentinel()));
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/ctor.parent.pass.cpp b/libcxx/test/std/ranges/range.adaptors/range.filter/sentinel/ctor.parent.pass.cpp
new file mode 100644 (file)
index 0000000..5cf9508
--- /dev/null
@@ -0,0 +1,61 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// UNSUPPORTED: libcpp-has-no-incomplete-ranges
+
+// constexpr explicit sentinel(filter_view&);
+
+#include <ranges>
+
+#include <array>
+#include <cassert>
+#include <type_traits>
+#include <utility>
+#include "test_iterators.h"
+#include "../types.h"
+
+template <class Iterator, class Sentinel = sentinel_wrapper<Iterator>>
+constexpr void test() {
+  using View = minimal_view<Iterator, Sentinel>;
+  using FilterView = std::ranges::filter_view<View, AlwaysTrue>;
+  using FilterSentinel = std::ranges::sentinel_t<FilterView>;
+
+  auto make_filter_view = [](auto begin, auto end, auto pred) {
+    View view{Iterator(begin), Sentinel(Iterator(end))};
+    return FilterView(std::move(view), pred);
+  };
+
+  std::array<int, 5> array{0, 1, 2, 3, 4};
+  FilterView view = make_filter_view(array.begin(), array.end(), AlwaysTrue{});
+
+  FilterSentinel sent(view);
+  assert(base(base(sent.base())) == base(base(view.end().base())));
+
+  static_assert(!std::is_constructible_v<FilterSentinel, FilterView const&>);
+  static_assert(!std::is_constructible_v<FilterSentinel, FilterView>);
+  static_assert( std::is_constructible_v<FilterSentinel, FilterView&> &&
+                !std::is_convertible_v<FilterView&, FilterSentinel>);
+}
+
+constexpr bool tests() {
+  test<cpp17_input_iterator<int*>>();
+  test<cpp20_input_iterator<int*>>();
+  test<forward_iterator<int*>>();
+  test<bidirectional_iterator<int*>>();
+  test<random_access_iterator<int*>>();
+  test<contiguous_iterator<int*>>();
+  test<int*>();
+  return true;
+}
+
+int main(int, char**) {
+  tests();
+  static_assert(tests());
+  return 0;
+}
diff --git a/libcxx/test/std/ranges/range.adaptors/range.filter/types.h b/libcxx/test/std/ranges/range.adaptors/range.filter/types.h
new file mode 100644 (file)
index 0000000..afac161
--- /dev/null
@@ -0,0 +1,104 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_FILTER_TYPES_H
+#define TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_FILTER_TYPES_H
+
+#include <ranges>
+#include <utility>
+
+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_;
+};
+
+struct AlwaysTrue {
+  template <typename T>
+  constexpr bool operator()(T const&) const { return true; }
+};
+
+template <class Iterator, class Sentinel>
+struct minimal_view : std::ranges::view_base {
+  constexpr explicit minimal_view(Iterator it, Sentinel sent)
+    : it_(base(std::move(it)))
+    , sent_(base(std::move(sent)))
+  { }
+
+  minimal_view(minimal_view&&) = default;
+  minimal_view& operator=(minimal_view&&) = default;
+
+  constexpr Iterator begin() const { return Iterator(it_); }
+  constexpr Sentinel end() const { return Sentinel(sent_); }
+
+private:
+  decltype(base(std::declval<Iterator>())) it_;
+  decltype(base(std::declval<Sentinel>())) sent_;
+};
+
+template <bool IsNoexcept>
+class NoexceptIterMoveInputIterator {
+  int *it_;
+
+public:
+  using iterator_category = std::input_iterator_tag;
+  using value_type = int;
+  using difference_type = typename std::iterator_traits<int *>::difference_type;
+  using pointer = int*;
+  using reference = int&;
+
+  NoexceptIterMoveInputIterator() = default;
+  explicit constexpr NoexceptIterMoveInputIterator(int *it) : it_(it) {}
+
+  friend constexpr decltype(auto) iter_move(const NoexceptIterMoveInputIterator& it) noexcept(IsNoexcept) {
+    return std::ranges::iter_move(it.it_);
+  }
+
+  friend constexpr int* base(const NoexceptIterMoveInputIterator& i) { return i.it_; }
+
+  constexpr reference operator*() const { return *it_; }
+  constexpr NoexceptIterMoveInputIterator& operator++() {++it_; return *this;}
+  constexpr NoexceptIterMoveInputIterator operator++(int)
+  { NoexceptIterMoveInputIterator tmp(*this); ++(*this); return tmp; }
+};
+
+template <bool IsNoexcept>
+class NoexceptIterSwapInputIterator {
+  int *it_;
+
+public:
+  using iterator_category = std::input_iterator_tag;
+  using value_type = int;
+  using difference_type = typename std::iterator_traits<int *>::difference_type;
+  using pointer = int*;
+  using reference = int&;
+
+  NoexceptIterSwapInputIterator() = default;
+  explicit constexpr NoexceptIterSwapInputIterator(int *it) : it_(it) {}
+
+  friend constexpr void iter_swap(const NoexceptIterSwapInputIterator& a, const NoexceptIterSwapInputIterator& b) noexcept(IsNoexcept) {
+    return std::ranges::iter_swap(a.it_, b.it_);
+  }
+
+  friend constexpr int* base(const NoexceptIterSwapInputIterator& i) { return i.it_; }
+
+  constexpr reference operator*() const { return *it_; }
+  constexpr NoexceptIterSwapInputIterator& operator++() {++it_; return *this;}
+  constexpr NoexceptIterSwapInputIterator operator++(int)
+  { NoexceptIterSwapInputIterator tmp(*this); ++(*this); return tmp; }
+};
+
+#endif // TEST_STD_RANGES_RANGE_ADAPTORS_RANGE_FILTER_TYPES_H