diff --git a/src/tools/std20/views.hpp b/src/tools/std20/views.hpp index 81ad310..7ed8cce 100644 --- a/src/tools/std20/views.hpp +++ b/src/tools/std20/views.hpp @@ -14,7 +14,7 @@ #include "tools/std20/views/drop_while.hpp" #include "tools/std20/views/elements.hpp" #include "tools/std20/views/empty.hpp" -//#include "tools/std20/views/filter.hpp" +#include "tools/std20/views/filter.hpp" #include "tools/std20/views/interface.hpp" #include "tools/std20/views/iota.hpp" //#include "tools/std20/views/istream.hpp" diff --git a/src/tools/std20/views/filter.hpp b/src/tools/std20/views/filter.hpp new file mode 100644 index 0000000..f1d5d23 --- /dev/null +++ b/src/tools/std20/views/filter.hpp @@ -0,0 +1,282 @@ +// nanorange/views/filter.hpp +// +// Copyright (c) 2019 Tristan Brindle (tcbrindle at gmail dot com) +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef NANORANGE_VIEWS_FILTER_HPP_INCLUDED +#define NANORANGE_VIEWS_FILTER_HPP_INCLUDED + +#include "tools/std20/algorithm/find.hpp" +#include "tools/std20/detail/views/semiregular_box.hpp" +#include "tools/std20/views/all.hpp" + +#include + +NANO_BEGIN_NAMESPACE + +namespace detail { + +template +constexpr auto filter_view_iter_cat_helper() +{ + using C = iterator_category_t>; + if constexpr (derived_from) { + return bidirectional_iterator_tag{}; + } else if constexpr (derived_from) { + return forward_iterator_tag{}; + } else { + return input_iterator_tag{}; + } +} + +constexpr inline auto as_ref = [](auto& pred) { + return [&p = pred] (auto&& arg) { + return nano::invoke(p, std::forward(arg)); + }; +}; + +} + +namespace filter_view_ { + +template +struct filter_view : view_interface> { + + // FIXME: GCC9 recursive constraint (?) problems again + // static_assert(input_range); + static_assert(input_iterator>); + static_assert(indirect_unary_predicate>); + static_assert(view); + static_assert(std::is_object_v); + +private: + V base_ = V(); + detail::semiregular_box pred_; + + struct iterator { + private: + iterator_t current_ = iterator_t(); + filter_view* parent_ = nullptr; + + public: + // using iterator_concept = ... + using iterator_category = + decltype(detail::filter_view_iter_cat_helper()); + using value_type = iter_value_t>; + using difference_type = iter_difference_t>; + // Extension: legacy typedefs + using pointer = iterator_t; + using reference = iter_reference_t>; + + iterator() = default; + constexpr iterator(filter_view& parent, iterator_t current) + : current_(std::move(current)), parent_(std::addressof(parent)) + {} + + constexpr iterator_t base() const { return current_; } + + constexpr iter_reference_t> operator*() const + { + return *current_; + } + + template + constexpr auto operator->() const + -> std::enable_if_t>, iterator_t> + { + return current_; + } + + constexpr iterator& operator++() + { + current_ = ranges::find_if(++current_, + ranges::end(parent_->base_), + detail::as_ref(*parent_->pred_)); + return *this; + } + + constexpr auto operator++(int) + { + if constexpr (forward_range) { + auto tmp = *this; + ++*this; + return tmp; + } else { + ++*this; + } + } + + template + constexpr auto operator--() + -> std::enable_if_t, iterator&> + { + do { + --current_; + } while (!nano::invoke(*parent_->pred_, *current_)); + return *this; + } + + template + constexpr auto operator--(int) + -> std::enable_if_t, iterator> + { + auto tmp = *this; + --*this; + return tmp; + } + + template + friend constexpr auto operator==(const iterator& x, const iterator& y) + -> std::enable_if_t>, bool> + { + return x.current_ == y.current_; + } + + template + friend constexpr auto operator!=(const iterator& x, const iterator& y) + -> std::enable_if_t>, bool> + { + return !(x == y); + } + + friend constexpr iter_rvalue_reference_t> + iter_move(const iterator& i) noexcept( + noexcept(ranges::iter_move(i.current_))) + { + return ranges::iter_move(i.current_); + } + + template + friend constexpr auto + iter_swap(const iterator& x, const iterator& y) noexcept( + noexcept(ranges::iter_swap(x.current_, y.current_))) + -> std::enable_if_t>> + { + ranges::iter_swap(x.current_, y.current_); + } + }; + + struct sentinel { + private: + sentinel_t end_ = sentinel_t(); + + public: + sentinel() = default; + + constexpr explicit sentinel(filter_view& parent) + : end_(ranges::end(parent.base_)) + {} + + constexpr sentinel_t base() const { return end_; } + + // Make these friend functions templates to keep MSVC happy +#if (defined(_MSC_VER) && _MSC_VER < 1922) + template +#endif + friend constexpr bool operator==(const iterator& i, const sentinel& s) + { + return i.base() == s.end_; + } + +#if (defined(_MSC_VER) && _MSC_VER < 1922) + template +#endif + friend constexpr bool operator==(const sentinel& s, const iterator& i) + { + return i == s; + } + +#if (defined(_MSC_VER) && _MSC_VER < 1922) + template +#endif + friend constexpr bool operator!=(const iterator& i, const sentinel& s) + { + return !(i == s); + } + +#if (defined(_MSC_VER) && _MSC_VER < 1922) + template +#endif + friend constexpr bool operator!=(const sentinel& s, const iterator& i) + { + return !(i == s); + } + }; + + std::optional begin_cache_{}; + +public: + filter_view() = default; + + constexpr filter_view(V base, Pred pred) + : base_(std::move(base)), pred_(std::move(pred)) + {} + + constexpr V base() const { return base_; } + + constexpr iterator begin() + { + if (begin_cache_) { + return *begin_cache_; + } + + assert(pred_.has_value()); + begin_cache_ = std::optional{ + iterator{*this, nano::find_if(base_, detail::as_ref(*pred_))}}; + return *begin_cache_; + } + + constexpr auto end() + { + if constexpr (common_range) { + return iterator{*this, ranges::end(base_)}; + } else { + return sentinel{*this}; + } + } +}; + +template +filter_view(R&&, Pred)->filter_view, Pred>; + +} + +using filter_view_::filter_view; + +namespace detail { + +struct filter_view_fn { + template + constexpr auto operator()(Pred pred) const + { + return detail::rao_proxy{[p = std::move(pred)] (auto&& r) mutable +#ifndef NANO_MSVC_LAMBDA_PIPE_WORKAROUND + -> decltype(filter_view{std::forward(r), std::declval()}) +#endif + { + return filter_view{std::forward(r), std::move(p)}; + }}; + } + + template + constexpr auto operator()(R&& r, Pred pred) const + noexcept(noexcept(filter_view{std::forward(r), std::move(pred)})) + -> decltype(filter_view{std::forward(r), std::move(pred)}) + { + return filter_view{std::forward(r), std::move(pred)}; + } + +}; + +} + +namespace views { + +NANO_INLINE_VAR(nano::detail::filter_view_fn, filter) + +} + +NANO_END_NAMESPACE + +#endif diff --git a/src/tools/std20/views/test/CMakeLists.txt b/src/tools/std20/views/test/CMakeLists.txt index f8ebc73..486be9a 100644 --- a/src/tools/std20/views/test/CMakeLists.txt +++ b/src/tools/std20/views/test/CMakeLists.txt @@ -4,6 +4,7 @@ add_cpp_test( std20.views.drop drop.test.cpp ) add_cpp_test( std20.views.drop_while drop_while.test.cpp ) add_cpp_test( std20.views.elements elements.test.cpp ) add_cpp_test( std20.views.empty empty.test.cpp ) +add_cpp_test( std20.views.filter filter.test.cpp ) add_cpp_test( std20.views.iota iota.test.cpp ) add_cpp_test( std20.views.join join.test.cpp ) add_cpp_test( std20.views.ref ref.test.cpp ) diff --git a/src/tools/std20/views/test/filter.test.cpp b/src/tools/std20/views/test/filter.test.cpp new file mode 100644 index 0000000..7865efd --- /dev/null +++ b/src/tools/std20/views/test/filter.test.cpp @@ -0,0 +1,210 @@ +// include Catch2 +#include + +// what we are testing +#include "tools/std20/views.hpp" + +// other includes +#include +#include +#include +#include "tools/std20/algorithm.hpp" + +// convenience typedefs +//using namespace njoy::tools; +namespace std20 = nano; + +SCENARIO( "filter_view" ) { + + const std::vector< int > equal = { -2, -1, 0 }; + const std::vector< int > modified = { -2, 0 }; + const std::vector< int > original_modified = { -2, 0, 2, 1, 2 }; + + auto filter = [] ( const auto& value ) { return value <= 0; }; + + GIVEN( "a container with forward iterators" ) { + + std::forward_list< int > values = { -2, -1, 0, 1, 2 }; + + WHEN( "when iterators are used" ) { + + auto chunk = values | std20::views::filter( filter ); + using Range = decltype(chunk); + using Iterator = nano::iterator_t< Range >; + + THEN( "the take_view satisfies the required concepts" ) { + + CHECK( std20::ranges::view< Range > ); + CHECK( ! std20::ranges::sized_range< Range > ); + CHECK( std20::ranges::forward_range< Range > ); + CHECK( ! std20::ranges::bidirectional_range< Range > ); + CHECK( ! std20::ranges::random_access_range< Range > ); + CHECK( ! std20::ranges::contiguous_range< Range > ); + CHECK( std20::ranges::common_range< Range > ); + } + + THEN( "the take_view range and iterator associated types are correct" ) { + + CHECK( std20::same_as< nano::ranges::range_value_t< Range >, int > ); + CHECK( std20::same_as< nano::ranges::range_reference_t< Range >, int& > ); + CHECK( std20::same_as< nano::ranges::range_difference_t< Range >, std::ptrdiff_t > ); + + CHECK( std20::same_as< nano::ranges::iter_value_t< Iterator >, int > ); + CHECK( std20::same_as< nano::ranges::iter_reference_t< Iterator >, int& > ); + CHECK( std20::same_as< nano::ranges::iter_difference_t< Iterator >, std::ptrdiff_t > ); + } + + THEN( "an take_view can be constructed and members can be tested" ) { + + // the following should not compile: no random access iterator + // CHECK( 5 == chunk.size() ); + + CHECK( false == chunk.empty() ); + CHECK( true == bool( chunk ) ); + + CHECK( std20::ranges::equal( chunk, equal ) ); + + // the following should not compile: no random access iterator + // CHECK( -2 == chunk[0] ); + + CHECK( -2 == chunk.front() ); + + // the following should not compile: no random access iterator + // CHECK( 2 == chunk.back() ); + + unsigned int counter = 0; + for ( auto&& value : chunk ) { + + value += counter; + ++counter; + } + CHECK( 3 == counter ); + + CHECK( std20::ranges::equal( chunk, modified ) ); + CHECK( std20::ranges::equal( values, original_modified ) ); + } // THEN + } // WHEN + } // GIVEN*/ + + GIVEN( "a container with bidirectional iterators" ) { + + std::list< int > values = { -2, -1, 0, 1, 2 }; + + WHEN( "when iterators are used" ) { + + auto chunk = values | std20::views::filter( filter ); + using Range = decltype(chunk); + using Iterator = nano::iterator_t< Range >; + + THEN( "the take_view satisfies the required concepts" ) { + + CHECK( std20::ranges::view< Range > ); + CHECK( ! std20::ranges::sized_range< Range > ); + CHECK( std20::ranges::forward_range< Range > ); + CHECK( std20::ranges::bidirectional_range< Range > ); + CHECK( ! std20::ranges::random_access_range< Range > ); + CHECK( ! std20::ranges::contiguous_range< Range > ); + CHECK( std20::ranges::common_range< Range > ); + } + + THEN( "the take_view range and iterator associated types are correct" ) { + + CHECK( std20::same_as< nano::ranges::range_value_t< Range >, int > ); + CHECK( std20::same_as< nano::ranges::range_reference_t< Range >, int& > ); + CHECK( std20::same_as< nano::ranges::range_difference_t< Range >, std::ptrdiff_t > ); + + CHECK( std20::same_as< nano::ranges::iter_value_t< Iterator >, int > ); + CHECK( std20::same_as< nano::ranges::iter_reference_t< Iterator >, int& > ); + CHECK( std20::same_as< nano::ranges::iter_difference_t< Iterator >, std::ptrdiff_t > ); + } + + THEN( "an take_view can be constructed and members can be tested" ) { + + // CHECK( 3 == chunk.size() ); + + CHECK( false == chunk.empty() ); + CHECK( true == bool( chunk ) ); + + CHECK( std20::ranges::equal( chunk, equal ) ); + + // the following should not compile: no random access iterator + // CHECK( -2 == chunk[0] ); + + unsigned int counter = 0; + for ( auto&& value : chunk ) { + + value += counter; + ++counter; + } + CHECK( 3 == counter ); + + CHECK( std20::ranges::equal( chunk, modified ) ); + CHECK( std20::ranges::equal( values, original_modified ) ); + } // THEN + } // WHEN + } // GIVEN + + GIVEN( "a container with random access iterators" ) { + + std::vector< int > values = { -2, -1, 0, 1, 2 }; + + WHEN( "when iterators are used" ) { + + auto chunk = values | std20::views::filter( filter ); + using Range = decltype(chunk); + using Iterator = nano::iterator_t< Range >; + + THEN( "the take_view satisfies the required concepts" ) { + + CHECK( std20::ranges::view< Range > ); + CHECK( ! std20::ranges::sized_range< Range > ); + CHECK( std20::ranges::forward_range< Range > ); + CHECK( std20::ranges::bidirectional_range< Range > ); + CHECK( ! std20::ranges::random_access_range< Range > ); + CHECK( ! std20::ranges::contiguous_range< Range > ); + CHECK( std20::ranges::common_range< Range > ); + } + + THEN( "the take_view range and iterator associated types are correct" ) { + + CHECK( std20::same_as< nano::ranges::range_value_t< Range >, int > ); + CHECK( std20::same_as< nano::ranges::range_reference_t< Range >, int& > ); + CHECK( std20::same_as< nano::ranges::range_difference_t< Range >, std::ptrdiff_t > ); + + CHECK( std20::same_as< nano::ranges::iter_value_t< Iterator >, int > ); + CHECK( std20::same_as< nano::ranges::iter_reference_t< Iterator >, int& > ); + CHECK( std20::same_as< nano::ranges::iter_difference_t< Iterator >, std::ptrdiff_t > ); + } + + THEN( "an take_view can be constructed and members can be tested" ) { + + + // CHECK( 3 == chunk.size() ); + + CHECK( false == chunk.empty() ); + CHECK( true == bool( chunk ) ); + + CHECK( std20::ranges::equal( chunk, equal ) ); + + // the following should not compile: no random access iterator + // CHECK( -2 == chunk[0] ); + // CHECK( -1 == chunk[1] ); + // CHECK( 0 == chunk[2] ); + + CHECK( -2 == chunk.front() ); + CHECK( 0 == chunk.back() ); + + unsigned int counter = 0; + for ( auto&& value : chunk ) { + + value += counter; + ++counter; + } + CHECK( 3 == counter ); + + CHECK( std20::ranges::equal( chunk, modified ) ); + CHECK( std20::ranges::equal( values, original_modified ) ); + } // THEN + } // WHEN + } // GIVEN*/ +} // SCENARIO