Skip to content

Commit

Permalink
✨ Add for_each and for_each_n to algorithms
Browse files Browse the repository at this point in the history
- Add variadic `for_each`
- Change tuple `for_each` to be `unrolled_for_each` on arrays
- Add `unrolled_enumerate` for arrays
- Constrain tuple algorithms
  • Loading branch information
elbeno authored and lukevalenty committed Apr 19, 2024
1 parent 639f1d3 commit 739f84c
Show file tree
Hide file tree
Showing 7 changed files with 141 additions and 38 deletions.
28 changes: 28 additions & 0 deletions docs/algorithm.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,34 @@
https://github.com/intel/cpp-std-extensions/blob/main/include/stdx/algorithm.hpp[`algorithm.hpp`]
provides an implementation of some algorithms.

=== `for_each` and `for_each_n`

`stdx::for_each` is similar to
https://en.cppreference.com/w/cpp/algorithm/for_each[`std::for_each`], but
variadic in its inputs.

[source,cpp]
----
template <typename InputIt, typename Operation, typename... InputItN>
constexpr auto for_each(InputIt first, InputIt last,
Operation op, InputItN... first_n) -> Operation;
----

NOTE: `stdx::for_each` is `constexpr` in C++20 and later, because it uses
https://en.cppreference.com/w/cpp/utility/functional/invoke[`std::invoke`].

`stdx::for_each_n` is just like `stdx::for_each`, but instead of taking two
iterators to delimit the input range, it takes an iterator and size.

[source,cpp]
----
template <typename InputIt, typename Size, typename Operation,
typename... InputItN>
constexpr auto for_each_n(InputIt first, Size n,
Operation op, InputItN... first_n) -> Operation;
----


=== `transform` and `transform_n`

`stdx::transform` is similar to
Expand Down
14 changes: 14 additions & 0 deletions docs/tuple_algorithms.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,13 @@ stdx::enumerate([] <auto Idx> (auto x) { std::cout << Idx << ':' << x << '\n'; }
----
NOTE: Like `for_each`, `enumerate` returns the function object passed to it.

`enumerate` is also available for `std::array`, but to be explicit it is called `unrolled_enumerate`:
[source,cpp]
----
auto a = std::array{1, 2, 3};
stdx::unrolled_enumerate([] <auto Idx> (auto x) { std::cout << Idx << ':' << x << '\n'; }, a);
----

=== `filter`

`filter` allows compile-time filtering of a tuple based on the types contained.
Expand Down Expand Up @@ -162,6 +169,13 @@ https://en.cppreference.com/w/cpp/algorithm/for_each[`std::for_each`],
`stdx::for_each` returns the function object passed to it. This can be useful
for stateful function objects.

`for_each` is also available for `std::array`, but to be explicit it is called `unrolled_for_each`:
[source,cpp]
----
auto a = std::array{1, 2, 3};
stdx::unrolled_for_each([] (auto x) { std::cout << x << '\n'; }, a);
----

=== `sort`

`sort` is used to sort a tuple by type name.
Expand Down
21 changes: 21 additions & 0 deletions include/stdx/algorithm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,27 @@ CONSTEXPR_INVOKE auto transform_n(InputIt first, Size n, OutputIt d_first,
return {d_first, first, first_n...};
}

template <typename InputIt, typename Operation, typename... InputItN>
CONSTEXPR_INVOKE auto for_each(InputIt first, InputIt last, Operation op,
InputItN... first_n) -> Operation {
while (first != last) {
std::invoke(op, *first, *first_n...);
static_cast<void>(++first), (static_cast<void>(++first_n), ...);
}
return op;
}

template <typename InputIt, typename Size, typename Operation,
typename... InputItN>
CONSTEXPR_INVOKE auto for_each_n(InputIt first, Size n, Operation op,
InputItN... first_n) -> Operation {
while (n-- > 0) {
std::invoke(op, *first, *first_n...);
static_cast<void>(++first), (static_cast<void>(++first_n), ...);
}
return op;
}

#undef CONSTEXPR_INVOKE
} // namespace v1
} // namespace stdx
32 changes: 16 additions & 16 deletions include/stdx/tuple.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ struct tuple_impl<std::index_sequence<Is...>, index_function_list<Fs...>, Ts...>
using base_t = typename element_helper<Fs...>::template element_t<I, T>;

public:
using common_tuple_comparable = void;
using is_tuple = void;

using base_t<Is, Ts>::ugly_iGet_clvr...;
using base_t<Is, Ts>::ugly_iGet_lvr...;
using base_t<Is, Ts>::ugly_iGet_rvr...;
Expand Down Expand Up @@ -394,12 +397,12 @@ using tuple_element_t = decltype(T::ugly_Value(index<I>));
template <typename T>
concept tuple_comparable = requires { typename T::common_tuple_comparable; };

template <typename... Ts>
struct tuple : detail::tuple_impl<std::index_sequence_for<Ts...>,
detail::index_function_list<>, Ts...> {
using common_tuple_comparable = void;
template <typename T>
concept tuplelike = requires { typename remove_cvref_t<T>::is_tuple; };

private:
template <typename... Ts>
class tuple : public detail::tuple_impl<std::index_sequence_for<Ts...>,
detail::index_function_list<>, Ts...> {
template <typename U>
requires(not tuple_comparable<U>)
[[nodiscard]] friend constexpr auto operator==(tuple const &, U const &)
Expand All @@ -413,11 +416,8 @@ struct tuple : detail::tuple_impl<std::index_sequence_for<Ts...>,
template <typename... Ts> tuple(Ts...) -> tuple<Ts...>;

template <typename IndexList, typename... Ts>
struct indexed_tuple
: detail::tuple_impl<std::index_sequence_for<Ts...>, IndexList, Ts...> {
using common_tuple_comparable = void;

private:
class indexed_tuple : public detail::tuple_impl<std::index_sequence_for<Ts...>,
IndexList, Ts...> {
template <typename U>
requires(not tuple_comparable<U>)
[[nodiscard]] friend constexpr auto operator==(indexed_tuple const &,
Expand All @@ -432,13 +432,13 @@ struct indexed_tuple
template <typename... Ts>
indexed_tuple(Ts...) -> indexed_tuple<detail::index_function_list<>, Ts...>;

template <std::size_t I, typename Tuple>
template <std::size_t I, tuplelike Tuple>
[[nodiscard]] constexpr auto get(Tuple &&t)
-> decltype(std::forward<Tuple>(t)[index<I>]) {
return std::forward<Tuple>(t)[index<I>];
}

template <typename T, typename Tuple>
template <typename T, tuplelike Tuple>
[[nodiscard]] constexpr auto get(Tuple &&t)
-> decltype(std::forward<Tuple>(t).get(tag<T>)) {
return std::forward<Tuple>(t).get(tag<T>);
Expand All @@ -454,7 +454,7 @@ constexpr auto make_indexed_tuple = []<typename... Ts>(Ts &&...ts) {
std::remove_cvref_t<Ts>...>{std::forward<Ts>(ts)...};
};

template <template <typename> typename... Fs, typename T>
template <template <typename> typename... Fs, tuplelike T>
constexpr auto apply_indices(T &&t) {
using tuple_t = std::remove_cvref_t<T>;
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
Expand All @@ -468,19 +468,19 @@ template <typename... Ts> constexpr auto forward_as_tuple(Ts &&...ts) {
return stdx::tuple<Ts &&...>{std::forward<Ts>(ts)...};
}

template <typename Op, typename T>
template <typename Op, tuplelike T>
constexpr auto apply(Op &&op, T &&t) -> decltype(auto) {
return std::forward<T>(t).apply(std::forward<Op>(op));
}

template <typename Op, typename T> constexpr auto transform(Op &&op, T &&t) {
template <typename Op, tuplelike T> constexpr auto transform(Op &&op, T &&t) {
return std::forward<T>(t).apply([&]<typename... Ts>(Ts &&...ts) {
return stdx::tuple<decltype(op(std::forward<Ts>(ts)))...>{
op(std::forward<Ts>(ts))...};
});
}

template <typename Op, typename T>
template <typename Op, tuplelike T>
constexpr auto for_each(Op &&op, T &&t) -> Op {
return std::forward<T>(t).apply([&]<typename... Ts>(Ts &&...ts) {
(op(std::forward<Ts>(ts)), ...);
Expand Down
52 changes: 32 additions & 20 deletions include/stdx/tuple_algorithms.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

namespace stdx {
inline namespace v1 {
template <typename... Ts> [[nodiscard]] constexpr auto tuple_cat(Ts &&...ts) {
template <tuplelike... Ts> [[nodiscard]] constexpr auto tuple_cat(Ts &&...ts) {
if constexpr (sizeof...(Ts) == 0) {
return stdx::tuple<>{};
} else if constexpr (sizeof...(Ts) == 1) {
Expand Down Expand Up @@ -52,7 +52,7 @@ template <typename... Ts> [[nodiscard]] constexpr auto tuple_cat(Ts &&...ts) {
}
}

template <template <typename T> typename Pred, typename T>
template <template <typename T> typename Pred, tuplelike T>
[[nodiscard]] constexpr auto filter(T &&t) {
using tuple_t = std::remove_cvref_t<T>;
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
Expand Down Expand Up @@ -90,8 +90,8 @@ constexpr auto invoke_at(auto &&op, Ts &&...ts) -> decltype(auto) {
}
} // namespace detail

template <template <typename> typename... Fs, typename Op, typename T,
typename... Ts>
template <template <typename> typename... Fs, typename Op, tuplelike T,
tuplelike... Ts>
constexpr auto transform(Op &&op, T &&t, Ts &&...ts) {
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
if constexpr (sizeof...(Fs) == 0) {
Expand All @@ -109,14 +109,20 @@ constexpr auto transform(Op &&op, T &&t, Ts &&...ts) {
}

template <typename Op, typename T, typename... Ts>
constexpr auto for_each(Op &&op, T &&t, Ts &&...ts) -> Op {
constexpr auto unrolled_for_each(Op &&op, T &&t, Ts &&...ts) -> Op {
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
(detail::invoke_at<Is>(op, std::forward<T>(t), std::forward<Ts>(ts)...),
...);
}(std::make_index_sequence<stdx::tuple_size_v<std::remove_cvref_t<T>>>{});
return op;
}

template <typename Op, tuplelike T, tuplelike... Ts>
constexpr auto for_each(Op &&op, T &&t, Ts &&...ts) -> Op {
return unrolled_for_each(std::forward<Op>(op), std::forward<T>(t),
std::forward<Ts>(ts)...);
}

namespace detail {
template <std::size_t I, typename... Ts>
constexpr auto invoke_with_idx_at(auto &&op, Ts &&...ts) -> decltype(auto) {
Expand All @@ -125,7 +131,7 @@ constexpr auto invoke_with_idx_at(auto &&op, Ts &&...ts) -> decltype(auto) {
} // namespace detail

template <typename Op, typename T, typename... Ts>
constexpr auto enumerate(Op &&op, T &&t, Ts &&...ts) -> Op {
constexpr auto unrolled_enumerate(Op &&op, T &&t, Ts &&...ts) -> Op {
[&]<std::size_t... Is>(std::index_sequence<Is...>) {
(detail::invoke_with_idx_at<Is>(op, std::forward<T>(t),
std::forward<Ts>(ts)...),
Expand All @@ -134,15 +140,21 @@ constexpr auto enumerate(Op &&op, T &&t, Ts &&...ts) -> Op {
return op;
}

template <typename F, typename T, typename... Ts>
template <typename Op, tuplelike T, tuplelike... Ts>
constexpr auto enumerate(Op &&op, T &&t, Ts &&...ts) -> Op {
return unrolled_enumerate(std::forward<Op>(op), std::forward<T>(t),
std::forward<Ts>(ts)...);
}

template <typename F, tuplelike T, tuplelike... Ts>
constexpr auto all_of(F &&f, T &&t, Ts &&...ts) -> bool {
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
return (... and detail::invoke_at<Is>(f, std::forward<T>(t),
std::forward<Ts>(ts)...));
}(std::make_index_sequence<stdx::tuple_size_v<std::remove_cvref_t<T>>>{});
}

template <typename F, typename T, typename... Ts>
template <typename F, tuplelike T, tuplelike... Ts>
constexpr auto any_of(F &&f, T &&t, Ts &&...ts) -> bool {
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
return (... or detail::invoke_at<Is>(f, std::forward<T>(t),
Expand All @@ -166,12 +178,12 @@ constexpr auto contains_type(
(std::is_same_v<T, Us> or ...)>;
} // namespace detail

template <typename Tuple, typename T>
template <tuplelike Tuple, typename T>
constexpr auto contains_type =
decltype(detail::contains_type<T>(std::declval<Tuple>()))::value;

template <template <typename> typename Proj = std::type_identity_t,
typename Tuple>
tuplelike Tuple>
[[nodiscard]] constexpr auto sort(Tuple &&t) {
using T = stdx::remove_cvref_t<Tuple>;
using P = std::pair<std::string_view, std::size_t>;
Expand All @@ -191,13 +203,13 @@ template <template <typename> typename Proj = std::type_identity_t,
}

namespace detail {
template <typename T, template <typename> typename Proj, std::size_t I>
template <tuplelike T, template <typename> typename Proj, std::size_t I>
[[nodiscard]] constexpr auto test_adjacent() -> bool {
return stdx::type_as_string<Proj<stdx::tuple_element_t<I, T>>>() ==
stdx::type_as_string<Proj<stdx::tuple_element_t<I + 1, T>>>();
}

template <typename T, template <typename> typename Proj = std::type_identity_t>
template <tuplelike T, template <typename> typename Proj = std::type_identity_t>
requires(tuple_size_v<T> > 1)
[[nodiscard]] constexpr auto count_chunks() {
auto count = std::size_t{1};
Expand All @@ -216,7 +228,7 @@ struct chunk {
-> bool = default;
};

template <typename T, template <typename> typename Proj = std::type_identity_t>
template <tuplelike T, template <typename> typename Proj = std::type_identity_t>
requires(tuple_size_v<T> > 1)
[[nodiscard]] constexpr auto create_chunks() {
auto index = std::size_t{};
Expand All @@ -238,7 +250,7 @@ template <typename T, template <typename> typename Proj = std::type_identity_t>
} // namespace detail

template <template <typename> typename Proj = std::type_identity_t,
typename Tuple>
tuplelike Tuple>
[[nodiscard]] constexpr auto chunk_by(Tuple &&t) {
using tuple_t = std::remove_cvref_t<Tuple>;
if constexpr (tuple_size_v<tuple_t> == 0) {
Expand All @@ -259,11 +271,11 @@ template <template <typename> typename Proj = std::type_identity_t,
}
}

template <typename Tuple> [[nodiscard]] constexpr auto chunk(Tuple &&t) {
template <tuplelike Tuple> [[nodiscard]] constexpr auto chunk(Tuple &&t) {
return chunk_by(std::forward<Tuple>(t));
}

template <typename... Ts> constexpr auto cartesian_product_copy(Ts &&...ts) {
template <tuplelike... Ts> constexpr auto cartesian_product_copy(Ts &&...ts) {
if constexpr (sizeof...(Ts) == 0) {
return make_tuple(tuple{});
} else {
Expand All @@ -284,7 +296,7 @@ template <typename... Ts> constexpr auto cartesian_product_copy(Ts &&...ts) {
}
}

template <typename... Ts> constexpr auto cartesian_product(Ts &&...ts) {
template <tuplelike... Ts> constexpr auto cartesian_product(Ts &&...ts) {
if constexpr (sizeof...(Ts) == 0) {
return make_tuple(tuple{});
} else {
Expand All @@ -305,18 +317,18 @@ template <typename... Ts> constexpr auto cartesian_product(Ts &&...ts) {
}
}

template <typename T> constexpr auto unique(T &&t) {
template <tuplelike T> constexpr auto unique(T &&t) {
return chunk(std::forward<T>(t)).apply([]<typename... Us>(Us &&...us) {
return tuple<tuple_element_t<0, Us>...>{
get<0>(std::forward<Us>(us))...};
});
}

template <typename T> constexpr auto to_sorted_set(T &&t) {
template <tuplelike T> constexpr auto to_sorted_set(T &&t) {
return unique(sort(std::forward<T>(t)));
}

template <typename Tuple> constexpr auto to_unsorted_set(Tuple &&t) {
template <tuplelike Tuple> constexpr auto to_unsorted_set(Tuple &&t) {
using T = stdx::remove_cvref_t<Tuple>;
using U = boost::mp11::mp_unique<T>;
return [&]<std::size_t... Is>(std::index_sequence<Is...>) {
Expand Down
19 changes: 19 additions & 0 deletions test/algorithm.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include <stdx/algorithm.hpp>
#include <stdx/tuple_algorithms.hpp>
#include <stdx/tuple_destructure.hpp>

#include <catch2/catch_test_macros.hpp>
Expand Down Expand Up @@ -44,3 +45,21 @@ TEST_CASE("n-ary transform_n", "[algorithm]") {
std::cbegin(input), std::cbegin(input));
CHECK(output == std::array{4, 8, 12, 16});
}

TEST_CASE("n-ary for_each", "[algorithm]") {
auto const input = std::array{1, 2, 3, 4};
auto output = decltype(input){};
stdx::for_each(
std::cbegin(input), std::cend(input),
[it = std::begin(output)](auto n) mutable { *it++ = n + 1; });
CHECK(output == std::array{2, 3, 4, 5});
}

TEST_CASE("n-ary for_each_n", "[algorithm]") {
auto const input = std::array{1, 2, 3, 4};
auto output = decltype(input){};
stdx::for_each_n(
std::cbegin(input), std::size(input),
[it = std::begin(output)](auto n) mutable { *it++ = n + 1; });
CHECK(output == std::array{2, 3, 4, 5});
}
Loading

0 comments on commit 739f84c

Please sign in to comment.