Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions libcxx/docs/ReleaseNotes/21.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ Implemented Papers
- P0767R1: Deprecate POD (`Github <https://github.com/llvm/llvm-project/issues/104013>`__)
- P1361R2: Integration of chrono with text formatting (`Github <https://github.com/llvm/llvm-project/issues/100014>`__)
- P2255R2: A type trait to detect reference binding to temporary (implemented the type traits only) (`Github <https://github.com/llvm/llvm-project/issues/105180>`__)
- P2322R6: ``ranges::fold`` (implemented ``ranges::fold_left_first`` and ``ranges::fold_left_first_with_iter``) (`Github <https://github.com/llvm/llvm-project/issues/105208>`__)

Improvements and New Features
-----------------------------
Expand Down
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 ``in_value_result``, ``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
53 changes: 53 additions & 0 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 Down Expand 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
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 @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <string_view>
#include <string>
#include <vector>
#include <optional>

#include "test_macros.h"
#include "test_range.h"
Expand All @@ -50,6 +51,8 @@
#endif

using std::ranges::fold_left;
using std::ranges::fold_left_first;
using std::ranges::fold_left_first_with_iter;
Copy link
Member

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.

Copy link
Contributor

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.

Copy link
Contributor

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?

using std::ranges::fold_left_with_iter;

template <class Result, class Range, class T>
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -186,26 +231,37 @@ 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;
n = y;
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;
Expand Down Expand Up @@ -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)));
}

{
Expand All @@ -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)));
}
}

Expand Down