Commit 29574a2b authored by Jan Wilken Dörrie's avatar Jan Wilken Dörrie Committed by Commit Bot

[base] Ranges: constexpr for_each

Following making invoke() constexpr this change also adds constexpr
support to ranges::for_each. This is done by no longer dispatching to
the std:: implementation, but rather implementing the loop ourselves.
This change furthermore reduces the delta to C++20 by using the right
return type for ranges::for_each, as well as adding an implementation of
range::for_each_n.

Bug: 1071094
Change-Id: Ic619f0c9926ef9c3e9fda4a67ab5e02bb5262402
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2325920Reviewed-by: default avatarPeter Kasting <pkasting@chromium.org>
Commit-Queue: Jan Wilken Dörrie <jdoerrie@chromium.org>
Cr-Commit-Position: refs/heads/master@{#792848}
parent 656c2b1d
...@@ -119,11 +119,10 @@ could be addressed by either upgrading Chromium's STL to C++20, or implementing ...@@ -119,11 +119,10 @@ could be addressed by either upgrading Chromium's STL to C++20, or implementing
`constexpr` versions of some of these algorithms ourselves. `constexpr` versions of some of these algorithms ourselves.
### Lack of post C++14 algorithms ### Lack of post C++14 algorithms
Since all algorithms in `util::ranges` dispatch to their C++14 equivalent, Since most algorithms in `util::ranges` dispatch to their C++14 equivalent, some
`std::` algorithms that are not present in C++14 have no implementation in `std::` algorithms that are not present in C++14 have no implementation in
`util::ranges`. This list of algorithms includes the following: `util::ranges`. This list of algorithms includes the following:
- [`std::for_each_n`](https://en.cppreference.com/w/cpp/algorithm/for_each_n) (added in C++17)
- [`std::sample`](https://en.cppreference.com/w/cpp/algorithm/sample) (added in C++17) - [`std::sample`](https://en.cppreference.com/w/cpp/algorithm/sample) (added in C++17)
- [`std::clamp`](https://en.cppreference.com/w/cpp/algorithm/clamp) (added in C++17) - [`std::clamp`](https://en.cppreference.com/w/cpp/algorithm/clamp) (added in C++17)
......
...@@ -154,6 +154,34 @@ using range_category_t = iterator_category_t<iterator_t<Range>>; ...@@ -154,6 +154,34 @@ using range_category_t = iterator_category_t<iterator_t<Range>>;
} // namespace internal } // namespace internal
// C++14 implementation of std::ranges::in_fun_result. Note the because C++14
// lacks the `no_unique_address` attribute it is commented out.
//
// Reference: https://wg21.link/algorithms.results#:~:text=in_fun_result
template <typename I, typename F>
struct in_fun_result {
/* [[no_unique_address]] */ I in;
/* [[no_unique_address]] */ F fun;
template <typename I2,
typename F2,
std::enable_if_t<std::is_convertible<const I&, I2>{} &&
std::is_convertible<const F&, F2>{}>>
constexpr operator in_fun_result<I2, F2>() const& {
return {in, fun};
}
template <typename I2,
typename F2,
std::enable_if_t<std::is_convertible<I, I2>{} &&
std::is_convertible<F, F2>{}>>
constexpr operator in_fun_result<I2, F2>() && {
return {std::move(in), std::move(fun)};
}
};
// TODO(crbug.com/1071094): Implement the other result types.
// [alg.nonmodifying] Non-modifying sequence operations // [alg.nonmodifying] Non-modifying sequence operations
// Reference: https://wg21.link/alg.nonmodifying // Reference: https://wg21.link/alg.nonmodifying
...@@ -298,13 +326,14 @@ constexpr bool none_of(Range&& range, Pred pred, Proj proj = {}) { ...@@ -298,13 +326,14 @@ constexpr bool none_of(Range&& range, Pred pred, Proj proj = {}) {
// [alg.foreach] For each // [alg.foreach] For each
// Reference: https://wg21.link/alg.foreach // Reference: https://wg21.link/alg.foreach
// Reference: https://wg21.link/algorithm.syn#:~:text=for_each_result
template <typename I, typename F>
using for_each_result = in_fun_result<I, F>;
// Effects: Calls `invoke(f, invoke(proj, *i))` for every iterator `i` in the // Effects: Calls `invoke(f, invoke(proj, *i))` for every iterator `i` in the
// range `[first, last)`, starting from `first` and proceeding to `last - 1`. // range `[first, last)`, starting from `first` and proceeding to `last - 1`.
// //
// Returns: `f` // Returns: `{last, std::move(f)}`.
// Note: std::ranges::for_each(I first,...) returns a for_each_result, rather
// than an invocable. For simplicitly we match std::for_each's return type
// instead.
// //
// Complexity: Applies `f` and `proj` exactly `last - first` times. // Complexity: Applies `f` and `proj` exactly `last - first` times.
// //
...@@ -319,21 +348,16 @@ constexpr auto for_each(InputIterator first, ...@@ -319,21 +348,16 @@ constexpr auto for_each(InputIterator first,
InputIterator last, InputIterator last,
Fun f, Fun f,
Proj proj = {}) { Proj proj = {}) {
std::for_each(first, last, [&f, &proj](auto&& arg) { for (; first != last; ++first)
return invoke(f, invoke(proj, std::forward<decltype(arg)>(arg))); invoke(f, invoke(proj, *first));
}); return for_each_result<InputIterator, Fun>{first, std::move(f)};
return f;
} }
// Effects: Calls `invoke(f, invoke(proj, *i))` for every iterator `i` in the // Effects: Calls `invoke(f, invoke(proj, *i))` for every iterator `i` in the
// range `range`, starting from `begin(range)` and proceeding to `end(range) - // range `range`, starting from `begin(range)` and proceeding to `end(range) -
// 1`. // 1`.
// //
// Returns: `f` // Returns: `{last, std::move(f)}`.
// Note: std::ranges::for_each(R&& r,...) returns a for_each_result, rather
// than an invocable. For simplicitly we match std::for_each's return type
// instead.
// //
// Complexity: Applies `f` and `proj` exactly `size(range)` times. // Complexity: Applies `f` and `proj` exactly `size(range)` times.
// //
...@@ -349,6 +373,35 @@ constexpr auto for_each(Range&& range, Fun f, Proj proj = {}) { ...@@ -349,6 +373,35 @@ constexpr auto for_each(Range&& range, Fun f, Proj proj = {}) {
std::move(f), std::move(proj)); std::move(f), std::move(proj));
} }
// Reference: https://wg21.link/algorithm.syn#:~:text=for_each_n_result
template <typename I, typename F>
using for_each_n_result = in_fun_result<I, F>;
// Preconditions: `n >= 0` is `true`.
//
// Effects: Calls `invoke(f, invoke(proj, *i))` for every iterator `i` in the
// range `[first, first + n)` in order.
//
// Returns: `{first + n, std::move(f)}`.
//
// Remarks: If `f` returns a result, the result is ignored.
//
// Reference: https://wg21.link/alg.foreach#:~:text=ranges::for_each_n
template <typename InputIterator,
typename Size,
typename Fun,
typename Proj = identity,
typename = internal::iterator_category_t<InputIterator>>
constexpr auto for_each_n(InputIterator first, Size n, Fun f, Proj proj = {}) {
while (n > 0) {
invoke(f, invoke(proj, *first));
++first;
--n;
}
return for_each_n_result<InputIterator, Fun>{first, std::move(f)};
}
// [alg.find] Find // [alg.find] Find
// Reference: https://wg21.link/alg.find // Reference: https://wg21.link/alg.find
......
...@@ -107,17 +107,38 @@ TEST(RangesTest, ForEach) { ...@@ -107,17 +107,38 @@ TEST(RangesTest, ForEach) {
auto times_two = [](int& i) { i *= 2; }; auto times_two = [](int& i) { i *= 2; };
int array[] = {0, 1, 2, 3, 4, 5}; int array[] = {0, 1, 2, 3, 4, 5};
ranges::for_each(array, array + 3, times_two); auto result = ranges::for_each(array, array + 3, times_two);
EXPECT_EQ(result.in, array + 3);
EXPECT_EQ(result.fun, times_two);
EXPECT_THAT(array, ElementsAre(0, 2, 4, 3, 4, 5)); EXPECT_THAT(array, ElementsAre(0, 2, 4, 3, 4, 5));
ranges::for_each(array + 3, array + 6, times_two); ranges::for_each(array + 3, array + 6, times_two);
EXPECT_EQ(result.in, array + 3);
EXPECT_EQ(result.fun, times_two);
EXPECT_THAT(array, ElementsAre(0, 2, 4, 6, 8, 10)); EXPECT_THAT(array, ElementsAre(0, 2, 4, 6, 8, 10));
EXPECT_EQ(times_two, ranges::for_each(array, times_two)); EXPECT_EQ(times_two, ranges::for_each(array, times_two).fun);
EXPECT_THAT(array, ElementsAre(0, 4, 8, 12, 16, 20)); EXPECT_THAT(array, ElementsAre(0, 4, 8, 12, 16, 20));
Int values[] = {{0}, {2}, {4}, {5}}; Int values[] = {0, 2, 4, 5};
EXPECT_EQ(times_two, ranges::for_each(values, times_two, &Int::value)); EXPECT_EQ(times_two, ranges::for_each(values, times_two, &Int::value).fun);
EXPECT_THAT(values,
ElementsAre(Field(&Int::value, 0), Field(&Int::value, 4),
Field(&Int::value, 8), Field(&Int::value, 10)));
}
TEST(RangesTest, ForEachN) {
auto times_two = [](int& i) { i *= 2; };
int array[] = {0, 1, 2, 3, 4, 5};
auto result = ranges::for_each_n(array, 3, times_two);
EXPECT_EQ(result.in, array + 3);
EXPECT_EQ(result.fun, times_two);
EXPECT_THAT(array, ElementsAre(0, 2, 4, 3, 4, 5));
Int values[] = {0, 2, 4, 5};
EXPECT_EQ(times_two,
ranges::for_each_n(values, 4, times_two, &Int::value).fun);
EXPECT_THAT(values, EXPECT_THAT(values,
ElementsAre(Field(&Int::value, 0), Field(&Int::value, 4), ElementsAre(Field(&Int::value, 0), Field(&Int::value, 4),
Field(&Int::value, 8), Field(&Int::value, 10))); Field(&Int::value, 8), Field(&Int::value, 10)));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment