From 9a1f91162c3e2e598c43d23906ff14747ff92489 Mon Sep 17 00:00:00 2001 From: Guekka <39066502+Guekka@users.noreply.github.com> Date: Wed, 17 Jul 2024 11:13:53 +0200 Subject: [PATCH] implement filter_map Co-authored-by: Tristan Brindle --- include/flux.hpp | 1 + include/flux/core/inline_sequence_base.hpp | 10 ++ include/flux/op/filter_map.hpp | 68 +++++++++ test/CMakeLists.txt | 1 + test/test_filter_map.cpp | 164 +++++++++++++++++++++ 5 files changed, 244 insertions(+) create mode 100644 include/flux/op/filter_map.hpp create mode 100644 test/test_filter_map.cpp diff --git a/include/flux.hpp b/include/flux.hpp index 3721fa97..40e87701 100644 --- a/include/flux.hpp +++ b/include/flux.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include diff --git a/include/flux/core/inline_sequence_base.hpp b/include/flux/core/inline_sequence_base.hpp index b4162ba3..03b1ebf4 100644 --- a/include/flux/core/inline_sequence_base.hpp +++ b/include/flux/core/inline_sequence_base.hpp @@ -7,6 +7,7 @@ #define FLUX_CORE_INLINE_SEQUENCE_BASE_HPP_INCLUDED #include +#include #include @@ -271,6 +272,15 @@ struct inline_sequence_base { [[nodiscard]] constexpr auto filter(Pred pred) &&; + template + requires (std::invocable> && + detail::optional_like>>) + [[nodiscard]] + constexpr auto filter_map(Func func) &&; + + [[nodiscard]] + constexpr auto filter_deref() && requires detail::optional_like>; + [[nodiscard]] constexpr auto flatten() && requires sequence>; diff --git a/include/flux/op/filter_map.hpp b/include/flux/op/filter_map.hpp new file mode 100644 index 00000000..2b2cf2ae --- /dev/null +++ b/include/flux/op/filter_map.hpp @@ -0,0 +1,68 @@ + +// Copyright (c) 2024 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 FLUX_OP_FILTER_MAP_HPP_INCLUDED +#define FLUX_OP_FILTER_MAP_HPP_INCLUDED + +#include +#include + +namespace flux { + + namespace detail { + + struct filter_map_fn { + // If dereffing the optional would give us an rvalue reference, + // prevent a probable dangling reference by returning by value instead + template + using strip_rvalue_ref_t = std::conditional_t< + std::is_rvalue_reference_v, std::remove_reference_t, T>; + template + requires (std::invocable> && + optional_like>>>) + constexpr auto operator()(Seq&& seq, Func func) const + { + return flux::map(FLUX_FWD(seq), std::move(func)) + .filter([](auto&& opt) { return static_cast(opt); }) + .map([](auto&& opt) -> strip_rvalue_ref_t { + return *FLUX_FWD(opt); + }); + } + }; + } // namespace detail + + FLUX_EXPORT inline constexpr auto filter_map = detail::filter_map_fn{}; + + template + template + requires (std::invocable> && + detail::optional_like>>) + constexpr auto inline_sequence_base::filter_map(Func func) && + { + return flux::filter_map(derived(), std::move(func)); + } + + namespace detail + { + struct filter_deref_fn { + template + requires optional_like> + constexpr auto operator()(Seq&& seq) const + { + return filter_map(FLUX_FWD(seq), [](auto&& opt) -> decltype(auto) { return FLUX_FWD(opt); }); + } + }; + } // namespace detail + + FLUX_EXPORT inline constexpr auto filter_deref = detail::filter_deref_fn{}; + + template + constexpr auto inline_sequence_base::filter_deref() && requires detail::optional_like> + { + return flux::filter_deref(derived()); + } +} // namespace flux + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7f7e58b0..fe5d47d9 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -43,6 +43,7 @@ add_executable(test-flux test_equal.cpp test_fill.cpp test_filter.cpp + test_filter_map.cpp test_find.cpp test_find_min_max.cpp test_flatten.cpp diff --git a/test/test_filter_map.cpp b/test/test_filter_map.cpp new file mode 100644 index 00000000..737b8e28 --- /dev/null +++ b/test/test_filter_map.cpp @@ -0,0 +1,164 @@ + +// Copyright (c) 2024 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) + +#include "catch.hpp" + +#include +#include + +#include "test_utils.hpp" + +namespace { + +constexpr auto is_even_opt = [](int i) { return i % 2 == 0 ? std::optional{i} : std::nullopt; }; + +struct Pair { + int i; + bool ok; + + [[nodiscard]] constexpr auto map_if_ok() const { return ok ? std::optional{*this} : std::nullopt; } + + constexpr bool operator==(const Pair&) const = default; +}; + +using filter_fn = decltype(flux::filter_map); + +// int is not a sequence +static_assert(not std::invocable); +// int is not a function +static_assert(not std::invocable); +// "func" does not return optional_like +static_assert(not std::invocable); +// Incompatible predicate +static_assert(not std::invocable); + +constexpr bool test_filter() +{ + // Basic filtering + { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + auto filtered = flux::filter_map(flux::ref(arr), is_even_opt); + using F = decltype(filtered); + static_assert(flux::sequence); + static_assert(flux::bidirectional_sequence); + static_assert(flux::bounded_sequence); + static_assert(not flux::ordered_cursor); + static_assert(not flux::sized_sequence); + + static_assert(flux::sequence); + static_assert(flux::bidirectional_sequence); + static_assert(flux::bounded_sequence); + static_assert(not flux::ordered_cursor); + static_assert(not flux::sized_sequence); + + STATIC_CHECK(check_equal(filtered, {0, 2, 4, 6, 8})); + STATIC_CHECK(check_equal(std::as_const(filtered), {0, 2, 4, 6, 8})); + } + + // A predicate that always returns true returns what it was given + { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + auto filtered = flux::filter_map(flux::ref(arr), [](auto&& i) { return std::optional{i}; }); + + if (!check_equal(arr, filtered)) { + return false; + } + } + + // A predicate that always returns false returns an empty sequence + { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + auto filtered = flux::filter_map(flux::ref(arr), [](auto&&) -> std::optional { return std::nullopt; }); + + if (!filtered.is_empty()) { + return false; + } + } + + // We can use any optional_like such as a pointer + { + std::array arr{}; + arr[0] = new int(1); + arr[1] = nullptr; + arr[2] = new int(3); + arr[3] = nullptr; + + auto filtered = flux::filter_map(arr, [](auto ptr) { return ptr; }); + + if (!check_equal(filtered, {1, 3})) { + return false; + } + + for (const auto *ptr : arr) { + delete ptr; + } + } + + // ... Better expressed as filter_deref + { + std::array arr{}; + arr[0] = new int(1); + arr[1] = nullptr; + arr[2] = new int(3); + arr[3] = nullptr; + + auto filtered = flux::filter_deref(arr); + + if (!check_equal(filtered, {1, 3})) { + return false; + } + + for (const auto *ptr : arr) { + delete ptr; + } + } + + // We can use a PMF to filter_map + { + std::array pairs = { + Pair{1, true}, + {2, false}, + {3, true}, + {4, false} + }; + + auto f = flux::filter_map(pairs, &Pair::map_if_ok); + + if (!check_equal(f, {Pair{1, true}, Pair{3, true}})) { + return false; + } + } + + // Reversed sequences can be filtered + { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + auto filtered = flux::ref(arr).reverse().filter_map(is_even_opt); + + if (!check_equal(filtered, {8, 6, 4, 2, 0})) { + return false; + } + } + + // ... and filtered sequences can be reversed + { + int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + auto filtered = flux::filter_map(flux::ref(arr), is_even_opt).reverse(); + + if (!check_equal(filtered, {8, 6, 4, 2, 0})) { + return false; + } + } + + return true; +} +static_assert(test_filter()); + +} + +TEST_CASE("filter_map") +{ + bool result = test_filter(); + REQUIRE(result); +}