Skip to content

Commit

Permalink
implement filter_map
Browse files Browse the repository at this point in the history
Co-authored-by: Tristan Brindle <t.c.brindle@gmail.com>
  • Loading branch information
Guekka and tcbrindle committed Jul 17, 2024
1 parent d88fd97 commit 9a1f911
Show file tree
Hide file tree
Showing 5 changed files with 244 additions and 0 deletions.
1 change: 1 addition & 0 deletions include/flux.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
#include <flux/op/equal.hpp>
#include <flux/op/fill.hpp>
#include <flux/op/filter.hpp>
#include <flux/op/filter_map.hpp>
#include <flux/op/find.hpp>
#include <flux/op/find_min_max.hpp>
#include <flux/op/flatten.hpp>
Expand Down
10 changes: 10 additions & 0 deletions include/flux/core/inline_sequence_base.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#define FLUX_CORE_INLINE_SEQUENCE_BASE_HPP_INCLUDED

#include <flux/core/sequence_access.hpp>
#include <flux/core/simple_sequence_base.hpp>

#include <flux/op/requirements.hpp>

Expand Down Expand Up @@ -271,6 +272,15 @@ struct inline_sequence_base {
[[nodiscard]]
constexpr auto filter(Pred pred) &&;

template <typename Func>
requires (std::invocable<Func&, element_t<Derived>> &&
detail::optional_like<std::invoke_result_t<Func&, element_t<Derived>>>)
[[nodiscard]]
constexpr auto filter_map(Func func) &&;

[[nodiscard]]
constexpr auto filter_deref() && requires detail::optional_like<value_t<Derived>>;

[[nodiscard]]
constexpr auto flatten() && requires sequence<element_t<Derived>>;

Expand Down
68 changes: 68 additions & 0 deletions include/flux/op/filter_map.hpp
Original file line number Diff line number Diff line change
@@ -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 <flux/core.hpp>
#include <flux/op/map.hpp>

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 <typename T>
using strip_rvalue_ref_t = std::conditional_t<
std::is_rvalue_reference_v<T>, std::remove_reference_t<T>, T>;
template <adaptable_sequence Seq, typename Func>
requires (std::invocable<Func&, element_t<Seq>> &&
optional_like<std::remove_cvref_t<std::invoke_result_t<Func&, element_t<Seq>>>>)
constexpr auto operator()(Seq&& seq, Func func) const
{
return flux::map(FLUX_FWD(seq), std::move(func))
.filter([](auto&& opt) { return static_cast<bool>(opt); })
.map([](auto&& opt) -> strip_rvalue_ref_t<decltype(*FLUX_FWD(opt))> {
return *FLUX_FWD(opt);
});
}
};
} // namespace detail

FLUX_EXPORT inline constexpr auto filter_map = detail::filter_map_fn{};

template <typename D>
template <typename Func>
requires (std::invocable<Func&, element_t<D>> &&
detail::optional_like<std::invoke_result_t<Func&, element_t<D>>>)
constexpr auto inline_sequence_base<D>::filter_map(Func func) &&
{
return flux::filter_map(derived(), std::move(func));
}

namespace detail
{
struct filter_deref_fn {
template <adaptable_sequence Seq>
requires optional_like<value_t<Seq>>
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 <typename D>
constexpr auto inline_sequence_base<D>::filter_deref() && requires detail::optional_like<value_t<D>>
{
return flux::filter_deref(derived());
}
} // namespace flux

#endif
1 change: 1 addition & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
164 changes: 164 additions & 0 deletions test/test_filter_map.cpp
Original file line number Diff line number Diff line change
@@ -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 <array>
#include <utility>

#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<filter_fn, int, decltype(is_even_opt)>);
// int is not a function
static_assert(not std::invocable<filter_fn, int(&)[10], int>);
// "func" does not return optional_like
static_assert(not std::invocable<filter_fn, int(&)[10], decltype([](int) {})>);
// Incompatible predicate
static_assert(not std::invocable<filter_fn, int(&)[10], decltype([](int*) { return true; })>);

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<F>);
static_assert(flux::bidirectional_sequence<F>);
static_assert(flux::bounded_sequence<F>);
static_assert(not flux::ordered_cursor<F>);
static_assert(not flux::sized_sequence<F>);

static_assert(flux::sequence<F const>);
static_assert(flux::bidirectional_sequence<F const>);
static_assert(flux::bounded_sequence<F const>);
static_assert(not flux::ordered_cursor<F const>);
static_assert(not flux::sized_sequence<F const>);

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<int> { return std::nullopt; });

if (!filtered.is_empty()) {
return false;
}
}

// We can use any optional_like such as a pointer
{
std::array<int*, 4> 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<int*, 4> 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<Pair, 4> 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);
}

0 comments on commit 9a1f911

Please sign in to comment.