Skip to content

[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

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libcxx/docs/Status/Cxx23Papers.csv
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
"`P2286R8 <https://wg21.link/P2286R8>`__","Formatting Ranges","2022-07 (Virtual)","|Complete|","16",""
"`P2291R3 <https://wg21.link/P2291R3>`__","Add Constexpr Modifiers to Functions ``to_chars`` and ``from_chars`` for Integral Types in ``<charconv>`` Header","2022-07 (Virtual)","|Complete|","16",""
"`P2302R4 <https://wg21.link/P2302R4>`__","``std::ranges::contains``","2022-07 (Virtual)","|Complete|","19",""
"`P2322R6 <https://wg21.link/P2322R6>`__","``ranges::fold``","2022-07 (Virtual)","","",""
"`P2322R6 <https://wg21.link/P2322R6>`__","``ranges::fold``","2022-07 (Virtual)","|Partial|","","Only ``fold_left_with_iter``, ``fold_left``, ``fold_left_first_with_iter``, and ``fold_left_first`` are implemented."
"`P2374R4 <https://wg21.link/P2374R4>`__","``views::cartesian_product``","2022-07 (Virtual)","","",""
"`P2404R3 <https://wg21.link/P2404R3>`__","Move-only types for ``equality_comparable_with``, ``totally_ordered_with``, and ``three_way_comparable_with``","2022-07 (Virtual)","","",""
"`P2408R5 <https://wg21.link/P2408R5>`__","Ranges iterators as inputs to non-Ranges algorithms","2022-07 (Virtual)","","",""
Expand Down
57 changes: 53 additions & 4 deletions libcxx/include/__algorithm/ranges_fold.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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> && //
Expand All @@ -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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please avoid unrelated changes.

return fold_left_with_iter_result<_Ip, _Up>{std::move(__first), _Up(std::move(__init))};
}

_Up __result = std::invoke(__f, std::move(__init), *__first);
for (++__first; __first != __last; ++__first) {
for (++__first; __first != __last; ++__first)
__result = std::invoke(__f, std::move(__result), *__first);
}

return fold_left_with_iter_result<_Ip, _Up>{std::move(__first), std::move(__result)};
}
Expand Down Expand Up @@ -118,6 +120,53 @@ 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
Expand Down
21 changes: 21 additions & 0 deletions libcxx/include/algorithm
Original file line number Diff line number Diff line change
Expand Up @@ -941,6 +941,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

Expand All @@ -951,6 +960,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>
Expand Down
7 changes: 3 additions & 4 deletions libcxx/modules/std/algorithm.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,14 @@ void test() {
// expected-warning@-1{{ignoring return value of function declared with 'nodiscard' attribute}}
std::ranges::fold_left(iter, iter, 0, std::plus());
// expected-warning@-1{{ignoring return value of function declared with 'nodiscard' attribute}}
std::ranges::fold_left_first(range, std::plus());
// expected-warning@-1{{ignoring return value of function declared with 'nodiscard' attribute}}
std::ranges::fold_left_first(iter, iter, 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}}
std::ranges::fold_left_with_iter(range, 0, std::plus());
// expected-warning@-1{{ignoring return value of function declared with 'nodiscard' attribute}}
std::ranges::fold_left_with_iter(iter, iter, 0, std::plus());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,17 @@

// <algorithm>

// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20

// MSVC warning C4244: 'argument': conversion from 'double' to 'const int', possible loss of data
// ADDITIONAL_COMPILE_FLAGS(cl-style-warnings): /wd4244

// template<input_iterator I, sentinel_for<I> S, class T,
// indirectly-binary-left-foldable<T, I> F>
// constexpr see below ranges::fold_left_with_iter(I first, S last, T init, F f);
//
// template<input_range R, class T, indirectly-binary-left-foldable<T, iterator_t<R>> F>
// constexpr see below ranges::fold_left_with_iter(R&& r, T init, F f);
// indirectly-binary-left-foldable<T, I> F>
// constexpr auto fold_left(I first, S last, T init, F f); // since C++23

// template<input_iterator I, sentinel_for<I> S, class T,
// indirectly-binary-left-foldable<T, I> F>
// constexpr see below ranges::fold_left(I first, S last, T init, F f);
//
// template<input_range R, class T, indirectly-binary-left-foldable<T, iterator_t<R>> F>
// constexpr see below ranges::fold_left(R&& r, T init, F f);
// constexpr auto fold_left(R&& r, T init, F f); // since C++23

// REQUIRES: std-at-least-c++23

// MSVC warning C4244: 'argument': conversion from 'double' to 'const int', possible loss of data
// ADDITIONAL_COMPILE_FLAGS(cl-style-warnings): /wd4244

#include <algorithm>
#include <cassert>
Expand All @@ -49,16 +42,10 @@
# include <sstream>
#endif

using std::ranges::fold_left;
using std::ranges::fold_left_with_iter;

template <class Result, class Range, class T>
concept is_in_value_result =
std::same_as<Result, std::ranges::fold_left_with_iter_result<std::ranges::iterator_t<Range>, T>>;

template <class Result, class T>
concept is_dangling_with = std::same_as<Result, std::ranges::fold_left_with_iter_result<std::ranges::dangling, T>>;

struct Integer {
int value;

Expand All @@ -73,31 +60,14 @@ template <std::ranges::input_range R, class T, class F, std::equality_comparable
requires std::copyable<R>
constexpr void check_iterator(R& r, T const& init, F f, Expected const& expected) {
{
is_in_value_result<R, Expected> decltype(auto) result = fold_left_with_iter(r.begin(), r.end(), init, 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, Expected> decltype(auto) result = fold_left_with_iter(r.begin(), r.end(), init, f2);
assert(result.in == r.end());
assert(result.value == expected);
assert(telemetry.invocations == std::ranges::distance(r));
assert(telemetry.moves == 0);
assert(telemetry.copies == 1);
}

{
std::same_as<Expected> decltype(auto) result = fold_left(r.begin(), r.end(), init, f);
std::same_as<Expected> decltype(auto) result = std::ranges::fold_left(r.begin(), r.end(), init, f);
assert(result == expected);
}

{
auto telemetry = invocable_telemetry();
auto f2 = invocable_with_telemetry(f, telemetry);
std::same_as<Expected> decltype(auto) result = fold_left(r.begin(), r.end(), init, f2);
std::same_as<Expected> decltype(auto) result = std::ranges::fold_left(r.begin(), r.end(), init, f2);
assert(result == expected);
assert(telemetry.invocations == std::ranges::distance(r));
assert(telemetry.moves == 0);
Expand All @@ -108,31 +78,25 @@ constexpr void check_iterator(R& r, T const& init, F f, Expected const& expected
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) {
{
is_in_value_result<R, Expected> decltype(auto) result = fold_left_with_iter(r, init, f);
assert(result.in == r.end());
assert(result.value == expected);
}

{
auto telemetry = invocable_telemetry();
auto f2 = invocable_with_telemetry(f, telemetry);
std::same_as<Expected> decltype(auto) result = fold_left(r, init, f2);
std::same_as<Expected> decltype(auto) result = std::ranges::fold_left(r, init, f2);
assert(result == expected);
assert(telemetry.invocations == std::ranges::distance(r));
assert(telemetry.moves == 0);
assert(telemetry.copies == 1);
}

{
std::same_as<Expected> decltype(auto) result = fold_left(r, init, f);
std::same_as<Expected> decltype(auto) result = std::ranges::fold_left(r, init, f);
assert(result == expected);
}

{
auto telemetry = invocable_telemetry();
auto f2 = invocable_with_telemetry(f, telemetry);
std::same_as<Expected> decltype(auto) result = fold_left(r, init, f2);
std::same_as<Expected> decltype(auto) result = std::ranges::fold_left(r, init, f2);
assert(result == expected);
assert(telemetry.invocations == std::ranges::distance(r));
assert(telemetry.moves == 0);
Expand All @@ -143,34 +107,17 @@ constexpr void check_lvalue_range(R& r, T const& init, F f, Expected const& expe
template <std::ranges::input_range R, class T, class F, std::equality_comparable Expected>
requires std::copyable<R>
constexpr void check_rvalue_range(R& r, T const& init, F f, Expected const& expected) {
{
auto r2 = r;
is_dangling_with<Expected> decltype(auto) result = fold_left_with_iter(std::move(r2), init, f);
assert(result.value == expected);
}

{
auto telemetry = invocable_telemetry();
auto f2 = invocable_with_telemetry(f, telemetry);
auto r2 = r;
is_dangling_with<Expected> decltype(auto) result = fold_left_with_iter(std::move(r2), init, f2);
assert(result.value == expected);
assert(telemetry.invocations == std::ranges::distance(r));
assert(telemetry.moves == 0);
assert(telemetry.copies == 1);
}

{
auto r2 = r;
std::same_as<Expected> decltype(auto) result = fold_left(std::move(r2), init, f);
std::same_as<Expected> decltype(auto) result = std::ranges::fold_left(std::move(r2), init, f);
assert(result == expected);
}

{
auto telemetry = invocable_telemetry();
auto f2 = invocable_with_telemetry(f, telemetry);
auto r2 = r;
std::same_as<Expected> decltype(auto) result = fold_left(std::move(r2), init, f2);
std::same_as<Expected> decltype(auto) result = std::ranges::fold_left(std::move(r2), init, f2);
assert(result == expected);
assert(telemetry.invocations == std::ranges::distance(r));
assert(telemetry.moves == 0);
Expand Down Expand Up @@ -269,32 +216,13 @@ void runtime_only_test_case() {
{
auto input = std::istringstream(raw_data);
auto data = std::views::istream<std::string>(input);
is_in_value_result<std::ranges::basic_istream_view<std::string, char>, std::string> decltype(auto) result =
fold_left_with_iter(data.begin(), data.end(), init, std::plus());

assert(result.in == data.end());
assert(result.value == expected);
}

{
auto input = std::istringstream(raw_data);
auto data = std::views::istream<std::string>(input);
is_in_value_result<std::ranges::basic_istream_view<std::string, char>, std::string> decltype(auto) result =
fold_left_with_iter(data, init, std::plus());
assert(result.in == data.end());
assert(result.value == expected);
}

{
auto input = std::istringstream(raw_data);
auto data = std::views::istream<std::string>(input);
assert(fold_left(data.begin(), data.end(), init, std::plus()) == expected);
assert(std::ranges::fold_left(data.begin(), data.end(), init, std::plus()) == expected);
}

{
auto input = std::istringstream(raw_data);
auto data = std::views::istream<std::string>(input);
assert(fold_left(data, init, std::plus()) == expected);
assert(std::ranges::fold_left(data, init, std::plus()) == expected);
}
}
#endif
Expand Down
Loading