-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc++] Add ranges::fold_left_first
and ranges::fold_left_first_with_iter
#121558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Thank you for submitting a Pull Request (PR) to the LLVM Project! This PR will be automatically labeled and the relevant teams will be notified. If you wish to, you can add reviewers by using the "Reviewers" section on this page. If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers. If you have further questions, they may be answered by the LLVM GitHub User Guide. You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Mostly LGTM!
✅ With the latest revision this PR passed the C/C++ code formatter. |
@llvm/pr-subscribers-libcxx Author: None (JCGoran) ChangesImplementation mostly borrowed from #75259. Full diff: https://github.com/llvm/llvm-project/pull/121558.diff 5 Files Affected:
diff --git a/libcxx/include/__algorithm/ranges_fold.h b/libcxx/include/__algorithm/ranges_fold.h
index d2c39213985044..b94fb59f6090a9 100644
--- a/libcxx/include/__algorithm/ranges_fold.h
+++ b/libcxx/include/__algorithm/ranges_fold.h
@@ -28,6 +28,7 @@
#include <__type_traits/invoke.h>
#include <__utility/forward.h>
#include <__utility/move.h>
+#include <optional>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
@@ -62,6 +63,9 @@ struct in_value_result {
template <class _Ip, class _Tp>
using fold_left_with_iter_result = in_value_result<_Ip, _Tp>;
+template <class _Ip, class _Tp>
+using fold_left_first_with_iter_result = in_value_result<_Ip, _Tp>;
+
template <class _Fp, class _Tp, class _Ip, class _Rp, class _Up = decay_t<_Rp>>
concept __indirectly_binary_left_foldable_impl =
convertible_to<_Rp, _Up> && //
@@ -118,6 +122,55 @@ struct __fold_left {
};
inline constexpr auto fold_left = __fold_left();
+
+struct __fold_left_first_with_iter {
+ template <input_iterator _Ip, sentinel_for<_Ip> _Sp, __indirectly_binary_left_foldable<iter_value_t<_Ip>, _Ip> _Fp>
+ requires constructible_from<iter_value_t<_Ip>, iter_reference_t<_Ip>>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static constexpr auto operator()(_Ip __first, _Sp __last, _Fp __f) {
+ using _Up = decltype(fold_left(std::move(__first), __last, iter_value_t<_Ip>(*__first), __f));
+
+ // case of empty range
+ if (__first == __last) {
+ return fold_left_first_with_iter_result<_Ip, optional<_Up>>{std::move(__first), optional<_Up>()};
+ }
+
+ _Up __result(*__first);
+ for (++__first; __first != __last; ++__first) {
+ __result = std::invoke(__f, std::move(__result), *__first);
+ }
+
+ return fold_left_first_with_iter_result<_Ip, optional<_Up>>{std::move(__first), optional<_Up>(std::move(__result))};
+ }
+
+ template <input_range _Rp, __indirectly_binary_left_foldable<range_value_t<_Rp>, iterator_t<_Rp>> _Fp>
+ requires constructible_from<range_value_t<_Rp>, range_reference_t<_Rp>>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static constexpr auto operator()(_Rp&& __r, _Fp __f) {
+ auto __result = operator()(ranges::begin(__r), ranges::end(__r), std::ref(__f));
+
+ using _Up = decltype(fold_left(ranges::begin(__r), ranges::end(__r), range_value_t<_Rp>(*ranges::begin(__r)), __f));
+ return fold_left_first_with_iter_result<borrowed_iterator_t<_Rp>, optional<_Up>>{
+ std::move(__result.in), std::move(__result.value)};
+ }
+};
+
+inline constexpr auto fold_left_first_with_iter = __fold_left_first_with_iter();
+
+struct __fold_left_first {
+ template <input_iterator _Ip, sentinel_for<_Ip> _Sp, __indirectly_binary_left_foldable<iter_value_t<_Ip>, _Ip> _Fp>
+ requires constructible_from<iter_value_t<_Ip>, iter_reference_t<_Ip>>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static constexpr auto operator()(_Ip __first, _Sp __last, _Fp __f) {
+ return fold_left_first_with_iter(std::move(__first), std::move(__last), std::ref(__f)).value;
+ }
+
+ template <input_range _Rp, __indirectly_binary_left_foldable<range_value_t<_Rp>, iterator_t<_Rp>> _Fp>
+ requires constructible_from<range_value_t<_Rp>, range_reference_t<_Rp>>
+ [[nodiscard]] _LIBCPP_HIDE_FROM_ABI static constexpr auto operator()(_Rp&& __r, _Fp __f) {
+ return fold_left_first_with_iter(ranges::begin(__r), ranges::end(__r), std::ref(__f)).value;
+ }
+};
+
+inline constexpr auto fold_left_first = __fold_left_first();
+
} // namespace ranges
#endif // _LIBCPP_STD_VER >= 23
diff --git a/libcxx/include/algorithm b/libcxx/include/algorithm
index e593ae26ed6e24..5592387e454950 100644
--- a/libcxx/include/algorithm
+++ b/libcxx/include/algorithm
@@ -938,6 +938,15 @@ namespace ranges {
template<input_range R, class T, indirectly-binary-left-foldable<T, iterator_t<R>> F>
constexpr auto fold_left(R&& r, T init, F f); // since C++23
+ template<input_iterator I, sentinel_for<I> S,
+ indirectly-binary-left-foldable<T, I> F>
+ requires constructible_from<iter_value_t<I>, iter_reference_t<I>>
+ constexpr see below fold_left_first(I first, S last, F f); // since C++23
+
+ template<input_range R, indirectly-binary-left-foldable<T, iterator_t<R>> F>
+ requires constructible_from<range_value_t<R>, range_reference_t<R>>
+ constexpr see below fold_left_first(R&& r, F f); // since C++23
+
template<class I, class T>
using fold_left_with_iter_result = in_value_result<I, T>; // since C++23
@@ -948,6 +957,18 @@ namespace ranges {
template<input_range R, class T, indirectly-binary-left-foldable<T, iterator_t<R>> F>
constexpr see below fold_left_with_iter(R&& r, T init, F f); // since C++23
+ template<class I, class T>
+ using fold_left_first_with_iter_result = in_value_result<I, T>; // since C++23
+
+ template<input_iterator I, sentinel_for<I> S,
+ indirectly-binary-left-foldable<T, I> F>
+ requires constructible_from<iter_value_t<I>, iter_reference_t<I>>
+ constexpr see below fold_left_first_with_iter(I first, S last, F f); // since C++23
+
+ template<input_range R, indirectly-binary-left-foldable<T, iterator_t<R>> F>
+ requires constructible_from<range_value_t<R>, range_reference_t<R>>
+ constexpr see below fold_left_first_with_iter(R&& r, F f); // since C++23
+
template<forward_iterator I1, sentinel_for<I1> S1, forward_iterator I2, sentinel_for<I2> S2,
class Pred = ranges::equal_to, class Proj1 = identity, class Proj2 = identity>
requires indirectly_comparable<I1, I2, Pred, Proj1, Proj2>
diff --git a/libcxx/modules/std/algorithm.inc b/libcxx/modules/std/algorithm.inc
index 3c2139cd64ee49..20a3aea4fa8dab 100644
--- a/libcxx/modules/std/algorithm.inc
+++ b/libcxx/modules/std/algorithm.inc
@@ -162,15 +162,14 @@ export namespace std {
// [alg.fold], fold
using std::ranges::fold_left;
+ using std::ranges::fold_left_first;
+ using std::ranges::fold_left_first_with_iter;
+ using std::ranges::fold_left_first_with_iter_result;
using std::ranges::fold_left_with_iter;
using std::ranges::fold_left_with_iter_result;
# if 0
- using std::ranges::fold_left_first;
using std::ranges::fold_right;
using std::ranges::fold_right_last;
- using std::ranges::fold_left_with_iter;
- using std::ranges::fold_left_first_with_iter;
- using std::ranges::fold_left_first_with_iter;
# endif
#endif // _LIBCPP_STD_VER >= 23
} // namespace ranges
diff --git a/libcxx/test/libcxx/diagnostics/algorithm.nodiscard.verify.cpp b/libcxx/test/libcxx/diagnostics/algorithm.nodiscard.verify.cpp
index 14febc12a8a2d9..f4f14e513308ba 100644
--- a/libcxx/test/libcxx/diagnostics/algorithm.nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/diagnostics/algorithm.nodiscard.verify.cpp
@@ -392,5 +392,9 @@ void test() {
// expected-warning@-1{{ignoring return value of function declared with 'nodiscard' attribute}}
std::ranges::fold_left_with_iter(iter, iter, 0, std::plus());
// expected-warning@-1{{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::ranges::fold_left_first_with_iter(range, std::plus());
+ // expected-warning@-1{{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::ranges::fold_left_first_with_iter(iter, iter, std::plus());
+ // expected-warning@-1{{ignoring return value of function declared with 'nodiscard' attribute}}
#endif
}
diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/left_folds.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/left_folds.pass.cpp
index 4987ca9cac4ae0..c5b75a2e4ca3c2 100644
--- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/left_folds.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/left_folds.pass.cpp
@@ -39,6 +39,7 @@
#include <string_view>
#include <string>
#include <vector>
+#include <optional>
#include "test_macros.h"
#include "test_range.h"
@@ -50,6 +51,8 @@
#endif
using std::ranges::fold_left;
+using std::ranges::fold_left_first;
+using std::ranges::fold_left_first_with_iter;
using std::ranges::fold_left_with_iter;
template <class Result, class Range, class T>
@@ -105,6 +108,48 @@ constexpr void check_iterator(R& r, T const& init, F f, Expected const& expected
}
}
+template <std::ranges::input_range R, class F, std::equality_comparable Expected>
+ requires std::copyable<R>
+constexpr void check_iterator(R& r, F f, std::optional<Expected> const& expected) {
+ {
+ is_in_value_result<R, std::optional<Expected>> decltype(auto) result =
+ fold_left_first_with_iter(r.begin(), r.end(), f);
+ assert(result.in == r.end());
+ assert(result.value == expected);
+ }
+
+ {
+ auto telemetry = invocable_telemetry();
+ auto f2 = invocable_with_telemetry(f, telemetry);
+ is_in_value_result<R, std::optional<Expected>> decltype(auto) result =
+ fold_left_first_with_iter(r.begin(), r.end(), f2);
+ assert(result.in == r.end());
+ assert(result.value == expected);
+ if (result.value.has_value()) {
+ assert(telemetry.invocations == std::ranges::distance(r) - 1);
+ assert(telemetry.moves == 0);
+ assert(telemetry.copies == 1);
+ }
+ }
+
+ {
+ std::same_as<std::optional<Expected>> decltype(auto) result = fold_left_first(r.begin(), r.end(), f);
+ assert(result == expected);
+ }
+
+ {
+ auto telemetry = invocable_telemetry();
+ auto f2 = invocable_with_telemetry(f, telemetry);
+ std::same_as<std::optional<Expected>> decltype(auto) result = fold_left_first(r.begin(), r.end(), f2);
+ assert(result == expected);
+ if (result.has_value()) {
+ assert(telemetry.invocations == std::ranges::distance(r) - 1);
+ assert(telemetry.moves == 0);
+ assert(telemetry.copies == 1);
+ }
+ }
+}
+
template <std::ranges::input_range R, class T, class F, std::equality_comparable Expected>
requires std::copyable<R>
constexpr void check_lvalue_range(R& r, T const& init, F f, Expected const& expected) {
@@ -186,19 +231,29 @@ constexpr void check(R r, T const& init, F f, Expected const& expected) {
check_rvalue_range(r, init, f, expected);
}
+template <std::ranges::input_range R, class F, std::equality_comparable Expected>
+ requires std::copyable<R>
+constexpr void check(R r, F f, std::optional<Expected> const& expected) {
+ check_iterator(r, f, expected);
+}
+
constexpr void empty_range_test_case() {
auto const data = std::vector<int>{};
check(data, 100, std::plus(), 100);
check(data, -100, std::multiplies(), -100);
+ check(data, std::plus(), std::optional<int>());
check(data | std::views::take_while([](auto) { return false; }), 1.23, std::plus(), 1.23);
check(data, Integer(52), &Integer::plus, Integer(52));
+ check(data | std::views::take_while([](auto) { return false; }), std::plus(), std::optional<int>());
}
constexpr void common_range_test_case() {
auto const data = std::vector<int>{1, 2, 3, 4};
check(data, 0, std::plus(), triangular_sum(data));
check(data, 1, std::multiplies(), factorial(data.back()));
+ check(data, std::plus(), std::optional(triangular_sum(data)));
+ check(data, std::multiplies(), std::optional(factorial(data.back())));
auto multiply_with_prev = [n = 1](auto const x, auto const y) mutable {
auto const result = x * y * n;
@@ -206,6 +261,7 @@ constexpr void common_range_test_case() {
return static_cast<std::size_t>(result);
};
check(data, 1, multiply_with_prev, factorial(data.size()) * factorial(data.size() - 1));
+ check(data, multiply_with_prev, std::optional(factorial(data.size()) * factorial(data.size() - 1)));
auto fib = [n = 1](auto x, auto) mutable {
auto old_x = x;
@@ -237,6 +293,7 @@ constexpr void non_common_range_test_case() {
auto data = std::vector<std::string>{"five", "three", "two", "six", "one", "four"};
auto range = data | std::views::transform(parse);
check(range, 0, std::plus(), triangular_sum(range));
+ check(range, std::plus(), std::optional(triangular_sum(range)));
}
{
@@ -248,6 +305,7 @@ constexpr void non_common_range_test_case() {
auto range =
std::views::lazy_split(data, ' ') | std::views::transform(to_string_view) | std::views::transform(parse);
check(range, 0, std::plus(), triangular_sum(range));
+ check(range, std::plus(), std::optional(triangular_sum(range)));
}
}
|
Ping |
2 similar comments
Ping |
Ping |
I think we're very near to landing this. Let's add notes to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tentatively approving. I pushed changes which added release notes to the documentation. @JCGoran Please confirm whether these notes are fine.
ranges::fold_left_first
and ranges::fold_left_first_with_iter
ranges::fold_left_first
and ranges::fold_left_first_with_iter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this. In general it look good.
using std::ranges::fold_left_first; | ||
using std::ranges::fold_left_first_with_iter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We prefer not to do this.
Also we prefer to have one test per function overload.
For tests of a set of properties we sometimes use one test like for the nodiscard test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now I've split this file into four algorithms, each for one algorithm.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what should do next... Should we cover different overloads in different function call entries, or in different test files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good after addressing my last review comment.
Please apply that comment to all tests in this patch.
I like to have a quick look at the final tests.
constexpr void common_range_test_case() { | ||
auto const data = std::vector<int>{1, 2, 3, 4}; | ||
check(data, std::plus(), std::optional(triangular_sum(data))); | ||
check(data, std::multiplies(), std::optional(factorial(data.back()))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I missed this in the previous review. But I feel these tests are way too clever.
check(data, std::multiplies(), std::optional(factorial(data.back()))); | |
check(data, std::multiplies(), std::optional(24)); |
makes the test a lot easier to inspect. It also allows to test the multiplies
in the transform
test below
auto data = std::vector<std::string>{"five", "three", "two", "six", "one", "four"};
auto range = data | std::views::transform(parse);
check(range, std::plus(), std::optional(triangular_sum(range)));
check(data, std::multiplies(), std::optional(720)); // <<< This is now tested.
@@ -82,14 +86,12 @@ struct __fold_left_with_iter { | |||
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI static constexpr auto operator()(_Ip __first, _Sp __last, _Tp __init, _Fp __f) { | |||
using _Up = decay_t<invoke_result_t<_Fp&, _Tp, iter_reference_t<_Ip>>>; | |||
|
|||
if (__first == __last) { | |||
if (__first == __last) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please avoid unrelated changes.
@JCGoran IMO it makes sense to create a sub-issue in #105208 and use the GitHub syntax to associate the PR with it: https://docs.github.com/en/issues/tracking-your-work-with-issues/using-issues/linking-a-pull-request-to-an-issue |
Implementation mostly borrowed from #75259.