From 6359ec7edda3d565facebc13a6578d9489cac8e1 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 2 Jan 2025 15:00:01 +0100 Subject: [PATCH 01/16] Initial impl of `fold_left_first_with_iter` --- libcxx/include/__algorithm/ranges_fold.h | 51 +++++++++++++++++ libcxx/include/algorithm | 12 ++++ .../algorithm.nodiscard.verify.cpp | 4 ++ .../alg.fold/left_folds.pass.cpp | 57 +++++++++++++++++++ 4 files changed, 124 insertions(+) diff --git a/libcxx/include/__algorithm/ranges_fold.h b/libcxx/include/__algorithm/ranges_fold.h index d2c3921398504..91873e6bd340a 100644 --- a/libcxx/include/__algorithm/ranges_fold.h +++ b/libcxx/include/__algorithm/ranges_fold.h @@ -62,6 +62,9 @@ struct in_value_result { template using fold_left_with_iter_result = in_value_result<_Ip, _Tp>; +template +using fold_left_first_with_iter_result = in_value_result<_Ip, _Tp>; + template > concept __indirectly_binary_left_foldable_impl = convertible_to<_Rp, _Up> && // @@ -118,6 +121,54 @@ struct __fold_left { }; inline constexpr auto fold_left = __fold_left(); + +struct __fold_left_first_with_iter { + template _Sp, __indirectly_binary_left_foldable, _Ip> _Fp> + requires constructible_from, 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>()}; + } + + optional<_Up> __result(std::in_place, *__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 , iterator_t<_Rp>> _Fp> + requires constructible_from, 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, optional<_Up>>{std::move(__result.in), optional(std::move(__result.value))}; + } +}; + +inline constexpr auto fold_left_first_with_iter = __fold_left_first_with_iter(); + +struct __fold_left_first { + template _Sp, __indirectly_binary_left_foldable, _Ip> _Fp> + requires constructible_from, 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 , iterator_t<_Rp>> _Fp> + requires constructible_from, 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 e593ae26ed6e2..16728cbf0bcff 100644 --- a/libcxx/include/algorithm +++ b/libcxx/include/algorithm @@ -948,6 +948,18 @@ namespace ranges { template> F> constexpr see below fold_left_with_iter(R&& r, T init, F f); // since C++23 + template + using fold_left_first_with_iter_result = in_value_result>; // since C++23 + + template S, + indirectly-binary-left-foldable F> + requires constructible_from, iter_reference_t> + constexpr see below fold_left_first_with_iter(I first, S last, F f); // since C++23 + + template> F> + requires constructible_from, range_reference_t> + constexpr see below fold_left_first_with_iter(R&& r, F f); // since C++23 + template S1, forward_iterator I2, sentinel_for S2, class Pred = ranges::equal_to, class Proj1 = identity, class Proj2 = identity> requires indirectly_comparable diff --git a/libcxx/test/libcxx/diagnostics/algorithm.nodiscard.verify.cpp b/libcxx/test/libcxx/diagnostics/algorithm.nodiscard.verify.cpp index 14febc12a8a2d..f4f14e513308b 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 4987ca9cac4ae..22c5e1b8620a0 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 #include #include +#include #include "test_macros.h" #include "test_range.h" @@ -50,7 +51,9 @@ #endif using std::ranges::fold_left; +using std::ranges::fold_left_first; using std::ranges::fold_left_with_iter; +using std::ranges::fold_left_first_with_iter; template concept is_in_value_result = @@ -105,6 +108,47 @@ constexpr void check_iterator(R& r, T const& init, F f, Expected const& expected } } +template + requires std::copyable +constexpr void check_iterator(R& r, F f, std::optional const& expected) { + { + is_in_value_result> 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> 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> 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> 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 requires std::copyable constexpr void check_lvalue_range(R& r, T const& init, F f, Expected const& expected) { @@ -186,19 +230,29 @@ constexpr void check(R r, T const& init, F f, Expected const& expected) { check_rvalue_range(r, init, f, expected); } +template + requires std::copyable +constexpr void check(R r, F f, std::optional const& expected) { + check_iterator(r, f, expected); +} + constexpr void empty_range_test_case() { auto const data = std::vector{}; check(data, 100, std::plus(), 100); check(data, -100, std::multiplies(), -100); + check(data, std::plus(), std::optional()); 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()); } constexpr void common_range_test_case() { auto const data = std::vector{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 +260,7 @@ constexpr void common_range_test_case() { return static_cast(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 +292,7 @@ constexpr void non_common_range_test_case() { auto data = std::vector{"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 +304,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))); } } From 8eb9f22996e5b12c8567ac7840bb0ada90b5f16a Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 2 Jan 2025 22:29:19 +0100 Subject: [PATCH 02/16] Update --- libcxx/modules/std/algorithm.inc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libcxx/modules/std/algorithm.inc b/libcxx/modules/std/algorithm.inc index 3c2139cd64ee4..fbf260241e89a 100644 --- a/libcxx/modules/std/algorithm.inc +++ b/libcxx/modules/std/algorithm.inc @@ -164,13 +164,12 @@ export namespace std { using std::ranges::fold_left; 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_left_first_with_iter; + using std::ranges::fold_left_first_with_iter_result; +# if 0 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 From 8067ea1728a6861648c8a206324e7909e8695d77 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Thu, 2 Jan 2025 22:40:16 +0100 Subject: [PATCH 03/16] Update header --- libcxx/include/algorithm | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/libcxx/include/algorithm b/libcxx/include/algorithm index 16728cbf0bcff..5592387e45495 100644 --- a/libcxx/include/algorithm +++ b/libcxx/include/algorithm @@ -938,6 +938,15 @@ namespace ranges { template> F> constexpr auto fold_left(R&& r, T init, F f); // since C++23 + template S, + indirectly-binary-left-foldable F> + requires constructible_from, iter_reference_t> + constexpr see below fold_left_first(I first, S last, F f); // since C++23 + + template> F> + requires constructible_from, range_reference_t> + constexpr see below fold_left_first(R&& r, F f); // since C++23 + template using fold_left_with_iter_result = in_value_result; // since C++23 @@ -949,7 +958,7 @@ namespace ranges { constexpr see below fold_left_with_iter(R&& r, T init, F f); // since C++23 template - using fold_left_first_with_iter_result = in_value_result>; // since C++23 + using fold_left_first_with_iter_result = in_value_result; // since C++23 template S, indirectly-binary-left-foldable F> From a5515dba2f6d5fef5455d136d1dbccf20c340a4b Mon Sep 17 00:00:00 2001 From: JCGoran Date: Fri, 3 Jan 2025 11:38:48 +0100 Subject: [PATCH 04/16] Formatting --- libcxx/modules/std/algorithm.inc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcxx/modules/std/algorithm.inc b/libcxx/modules/std/algorithm.inc index fbf260241e89a..20a3aea4fa8da 100644 --- a/libcxx/modules/std/algorithm.inc +++ b/libcxx/modules/std/algorithm.inc @@ -162,11 +162,11 @@ export namespace std { // [alg.fold], fold using std::ranges::fold_left; - using std::ranges::fold_left_with_iter; - using std::ranges::fold_left_with_iter_result; 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_right; using std::ranges::fold_right_last; From 1cdc9bf38c8b825328d0c588c6000933154ba063 Mon Sep 17 00:00:00 2001 From: JCGoran Date: Fri, 3 Jan 2025 15:35:13 +0100 Subject: [PATCH 05/16] More formatting --- libcxx/include/__algorithm/ranges_fold.h | 11 +++---- .../alg.fold/left_folds.pass.cpp | 29 ++++++++++--------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/libcxx/include/__algorithm/ranges_fold.h b/libcxx/include/__algorithm/ranges_fold.h index 91873e6bd340a..b31ff7ca27b35 100644 --- a/libcxx/include/__algorithm/ranges_fold.h +++ b/libcxx/include/__algorithm/ranges_fold.h @@ -124,7 +124,7 @@ inline constexpr auto fold_left = __fold_left(); struct __fold_left_first_with_iter { template _Sp, __indirectly_binary_left_foldable, _Ip> _Fp> - requires constructible_from, iter_reference_t<_Ip>> + requires constructible_from, 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)); @@ -142,12 +142,13 @@ struct __fold_left_first_with_iter { } template , iterator_t<_Rp>> _Fp> - requires constructible_from, range_reference_t<_Rp>> + requires constructible_from, 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, optional<_Up>>{std::move(__result.in), optional(std::move(__result.value))}; + return fold_left_first_with_iter_result, optional<_Up>>{ + std::move(__result.in), optional(std::move(__result.value))}; } }; @@ -155,13 +156,13 @@ inline constexpr auto fold_left_first_with_iter = __fold_left_first_with_iter(); struct __fold_left_first { template _Sp, __indirectly_binary_left_foldable, _Ip> _Fp> - requires constructible_from, iter_reference_t<_Ip>> + requires constructible_from, 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 , iterator_t<_Rp>> _Fp> - requires constructible_from, range_reference_t<_Rp>> + requires constructible_from, 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; } 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 22c5e1b8620a0..c5b75a2e4ca3c 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 @@ -52,8 +52,8 @@ using std::ranges::fold_left; using std::ranges::fold_left_first; -using std::ranges::fold_left_with_iter; using std::ranges::fold_left_first_with_iter; +using std::ranges::fold_left_with_iter; template concept is_in_value_result = @@ -112,21 +112,23 @@ template constexpr void check_iterator(R& r, F f, std::optional const& expected) { { - is_in_value_result> decltype(auto) result = fold_left_first_with_iter(r.begin(), r.end(), f); + is_in_value_result> 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> decltype(auto) result = fold_left_first_with_iter(r.begin(), r.end(), f2); + auto telemetry = invocable_telemetry(); + auto f2 = invocable_with_telemetry(f, telemetry); + is_in_value_result> 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); + assert(telemetry.invocations == std::ranges::distance(r) - 1); + assert(telemetry.moves == 0); + assert(telemetry.copies == 1); } } @@ -136,19 +138,18 @@ constexpr void check_iterator(R& r, F f, std::optional const& expected } { - auto telemetry = invocable_telemetry(); - auto f2 = invocable_with_telemetry(f, telemetry); + auto telemetry = invocable_telemetry(); + auto f2 = invocable_with_telemetry(f, telemetry); std::same_as> 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); + assert(telemetry.invocations == std::ranges::distance(r) - 1); + assert(telemetry.moves == 0); + assert(telemetry.copies == 1); } } } - template requires std::copyable constexpr void check_lvalue_range(R& r, T const& init, F f, Expected const& expected) { From a5126a408b0dcd93810b5c94218a8c896ffff9be Mon Sep 17 00:00:00 2001 From: JCGoran Date: Fri, 3 Jan 2025 16:52:50 +0100 Subject: [PATCH 06/16] Apply suggested changes --- libcxx/include/__algorithm/ranges_fold.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libcxx/include/__algorithm/ranges_fold.h b/libcxx/include/__algorithm/ranges_fold.h index b31ff7ca27b35..3e9ddf13e4454 100644 --- a/libcxx/include/__algorithm/ranges_fold.h +++ b/libcxx/include/__algorithm/ranges_fold.h @@ -133,9 +133,9 @@ struct __fold_left_first_with_iter { return fold_left_first_with_iter_result<_Ip, optional<_Up>>{std::move(__first), optional<_Up>()}; } - optional<_Up> __result(std::in_place, *__first); + _Up __result(*__first); for (++__first; __first != __last; ++__first) { - *__result = std::invoke(__f, std::move(*__result), *__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))}; @@ -148,7 +148,7 @@ struct __fold_left_first_with_iter { 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, optional<_Up>>{ - std::move(__result.in), optional(std::move(__result.value))}; + std::move(__result.in), std::move(__result.value)}; } }; From 3816c4c4a42275f31d2d36a821f28497467a4fef Mon Sep 17 00:00:00 2001 From: JCGoran Date: Sun, 5 Jan 2025 19:55:53 +0100 Subject: [PATCH 07/16] Add missing header --- libcxx/include/__algorithm/ranges_fold.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libcxx/include/__algorithm/ranges_fold.h b/libcxx/include/__algorithm/ranges_fold.h index 3e9ddf13e4454..b94fb59f6090a 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 #if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER) # pragma GCC system_header From 7bac67a97e42a4878b69a1eca214425b6cc64895 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Wed, 19 Mar 2025 17:07:55 +0800 Subject: [PATCH 08/16] Add release notes --- libcxx/docs/ReleaseNotes/21.rst | 1 + libcxx/docs/Status/Cxx23Papers.csv | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst index f41cf9b51c292..a6df99b522c50 100644 --- a/libcxx/docs/ReleaseNotes/21.rst +++ b/libcxx/docs/ReleaseNotes/21.rst @@ -42,6 +42,7 @@ Implemented Papers - P0767R1: Deprecate POD (`Github `__) - P1361R2: Integration of chrono with text formatting (`Github `__) - P2255R2: A type trait to detect reference binding to temporary (implemented the type traits only) (`Github `__) +- P2322R6: ``ranges::fold`` (implemented ``ranges::fold_left_first`` and ``ranges::fold_left_first_with_iter``) (`Github `__) Improvements and New Features ----------------------------- diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv index 2589954729a30..27a9dd03d9210 100644 --- a/libcxx/docs/Status/Cxx23Papers.csv +++ b/libcxx/docs/Status/Cxx23Papers.csv @@ -65,7 +65,7 @@ "`P2286R8 `__","Formatting Ranges","2022-07 (Virtual)","|Complete|","16","" "`P2291R3 `__","Add Constexpr Modifiers to Functions ``to_chars`` and ``from_chars`` for Integral Types in ```` Header","2022-07 (Virtual)","|Complete|","16","" "`P2302R4 `__","``std::ranges::contains``","2022-07 (Virtual)","|Complete|","19","" -"`P2322R6 `__","``ranges::fold``","2022-07 (Virtual)","","","" +"`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 `__","``views::cartesian_product``","2022-07 (Virtual)","","","" "`P2404R3 `__","Move-only types for ``equality_comparable_with``, ``totally_ordered_with``, and ``three_way_comparable_with``","2022-07 (Virtual)","","","" "`P2408R5 `__","Ranges iterators as inputs to non-Ranges algorithms","2022-07 (Virtual)","","","" From abf6df7118cb5a2b783f759391749d2d0e752eb5 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Fri, 21 Mar 2025 16:59:10 +0800 Subject: [PATCH 09/16] Adjust release notes per review comments --- libcxx/docs/ReleaseNotes/21.rst | 1 - libcxx/docs/Status/Cxx23Papers.csv | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/libcxx/docs/ReleaseNotes/21.rst b/libcxx/docs/ReleaseNotes/21.rst index a6df99b522c50..f41cf9b51c292 100644 --- a/libcxx/docs/ReleaseNotes/21.rst +++ b/libcxx/docs/ReleaseNotes/21.rst @@ -42,7 +42,6 @@ Implemented Papers - P0767R1: Deprecate POD (`Github `__) - P1361R2: Integration of chrono with text formatting (`Github `__) - P2255R2: A type trait to detect reference binding to temporary (implemented the type traits only) (`Github `__) -- P2322R6: ``ranges::fold`` (implemented ``ranges::fold_left_first`` and ``ranges::fold_left_first_with_iter``) (`Github `__) Improvements and New Features ----------------------------- diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv index 27a9dd03d9210..46f4d91825dfa 100644 --- a/libcxx/docs/Status/Cxx23Papers.csv +++ b/libcxx/docs/Status/Cxx23Papers.csv @@ -65,7 +65,7 @@ "`P2286R8 `__","Formatting Ranges","2022-07 (Virtual)","|Complete|","16","" "`P2291R3 `__","Add Constexpr Modifiers to Functions ``to_chars`` and ``from_chars`` for Integral Types in ```` Header","2022-07 (Virtual)","|Complete|","16","" "`P2302R4 `__","``std::ranges::contains``","2022-07 (Virtual)","|Complete|","19","" -"`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." +"`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 `__","``views::cartesian_product``","2022-07 (Virtual)","","","" "`P2404R3 `__","Move-only types for ``equality_comparable_with``, ``totally_ordered_with``, and ``three_way_comparable_with``","2022-07 (Virtual)","","","" "`P2408R5 `__","Ranges iterators as inputs to non-Ranges algorithms","2022-07 (Virtual)","","","" From 2da3541e76f45ceb9bdd1f42d198c0067123cdaa Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Fri, 21 Mar 2025 17:00:57 +0800 Subject: [PATCH 10/16] Modify general test coverage for ranges algorithms --- .../libcxx/diagnostics/algorithm.nodiscard.verify.cpp | 8 ++++++-- .../customization.point.object/niebloid.compile.pass.cpp | 2 ++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/libcxx/test/libcxx/diagnostics/algorithm.nodiscard.verify.cpp b/libcxx/test/libcxx/diagnostics/algorithm.nodiscard.verify.cpp index f4f14e513308b..ca111494bdeb5 100644 --- a/libcxx/test/libcxx/diagnostics/algorithm.nodiscard.verify.cpp +++ b/libcxx/test/libcxx/diagnostics/algorithm.nodiscard.verify.cpp @@ -388,13 +388,17 @@ 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_with_iter(range, 0, std::plus()); + std::ranges::fold_left_first(range, 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()); + 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()); + // expected-warning@-1{{ignoring return value of function declared with 'nodiscard' attribute}} #endif } diff --git a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp index 5a2ee189641c7..faa1123f9caac 100644 --- a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp +++ b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp @@ -94,6 +94,8 @@ static_assert(test(std::ranges::find_last, a, 42)); static_assert(test(std::ranges::find_last_if, a, odd)); static_assert(test(std::ranges::find_last_if_not, a, odd)); static_assert(test(std::ranges::fold_left, a, 0, std::plus())); +static_assert(test(std::ranges::fold_left_first(a, std::plus()))); +static_assert(test(std::ranges::fold_left_first_with_iter(a, std::plus()))); static_assert(test(std::ranges::fold_left_with_iter, a, 0, std::plus())); #endif static_assert(test(std::ranges::for_each, a, odd)); From 8396d9e28e74a32859cc1324e238ca5a1ac5b555 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Fri, 21 Mar 2025 17:01:30 +0800 Subject: [PATCH 11/16] Code style change in `<__algorithm/ranges_fold.h>` --- libcxx/include/__algorithm/ranges_fold.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/libcxx/include/__algorithm/ranges_fold.h b/libcxx/include/__algorithm/ranges_fold.h index b94fb59f6090a..6fb6e2319aaf8 100644 --- a/libcxx/include/__algorithm/ranges_fold.h +++ b/libcxx/include/__algorithm/ranges_fold.h @@ -86,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>>; - if (__first == __last) { + if (__first == __last) 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)}; } @@ -130,14 +128,12 @@ struct __fold_left_first_with_iter { using _Up = decltype(fold_left(std::move(__first), __last, iter_value_t<_Ip>(*__first), __f)); // case of empty range - if (__first == __last) { + 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) { + 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))}; } From 9ea524df398f379a2e2e6deb9fab8f396b72fa63 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Fri, 21 Mar 2025 17:03:23 +0800 Subject: [PATCH 12/16] Split `left_folds.pass.cpp` into four files; each for an algorithm --- ...lds.pass.cpp => ranges.fold_left.pass.cpp} | 164 ++---------- .../alg.fold/ranges.fold_left_first.pass.cpp | 162 +++++++++++ .../ranges.fold_left_first_with_iter.pass.cpp | 170 ++++++++++++ .../ranges.fold_left_with_iter.pass.cpp | 253 ++++++++++++++++++ 4 files changed, 602 insertions(+), 147 deletions(-) rename libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/{left_folds.pass.cpp => ranges.fold_left.pass.cpp} (56%) create mode 100644 libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first.pass.cpp create mode 100644 libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first_with_iter.pass.cpp create mode 100644 libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_with_iter.pass.cpp diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/left_folds.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left.pass.cpp similarity index 56% rename from libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/left_folds.pass.cpp rename to libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left.pass.cpp index c5b75a2e4ca3c..21fe985d5e67a 100644 --- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/left_folds.pass.cpp +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left.pass.cpp @@ -8,24 +8,17 @@ // -// 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 S, class T, -// indirectly-binary-left-foldable F> -// constexpr see below ranges::fold_left_with_iter(I first, S last, T init, F f); -// -// template> F> -// constexpr see below ranges::fold_left_with_iter(R&& r, T init, F f); +// indirectly-binary-left-foldable F> +// constexpr auto fold_left(I first, S last, T init, F f); // since C++23 -// template S, class T, -// indirectly-binary-left-foldable F> -// constexpr see below ranges::fold_left(I first, S last, T init, F f); -// // template> 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 #include @@ -39,7 +32,6 @@ #include #include #include -#include #include "test_macros.h" #include "test_range.h" @@ -50,18 +42,10 @@ # include #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 concept is_in_value_result = std::same_as, T>>; -template -concept is_dangling_with = std::same_as>; - struct Integer { int value; @@ -76,31 +60,14 @@ template constexpr void check_iterator(R& r, T const& init, F f, Expected const& expected) { { - is_in_value_result 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 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 decltype(auto) result = fold_left(r.begin(), r.end(), init, f); + std::same_as 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 decltype(auto) result = fold_left(r.begin(), r.end(), init, f2); + std::same_as 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); @@ -108,61 +75,13 @@ constexpr void check_iterator(R& r, T const& init, F f, Expected const& expected } } -template - requires std::copyable -constexpr void check_iterator(R& r, F f, std::optional const& expected) { - { - is_in_value_result> 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> 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> 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> 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 requires std::copyable constexpr void check_lvalue_range(R& r, T const& init, F f, Expected const& expected) { - { - is_in_value_result 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 decltype(auto) result = fold_left(r, init, f2); + std::same_as decltype(auto) result = std::ranges::fold_left(r, init, f2); assert(result == expected); assert(telemetry.invocations == std::ranges::distance(r)); assert(telemetry.moves == 0); @@ -170,14 +89,14 @@ constexpr void check_lvalue_range(R& r, T const& init, F f, Expected const& expe } { - std::same_as decltype(auto) result = fold_left(r, init, f); + std::same_as 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 decltype(auto) result = fold_left(r, init, f2); + std::same_as decltype(auto) result = std::ranges::fold_left(r, init, f2); assert(result == expected); assert(telemetry.invocations == std::ranges::distance(r)); assert(telemetry.moves == 0); @@ -188,26 +107,9 @@ constexpr void check_lvalue_range(R& r, T const& init, F f, Expected const& expe template requires std::copyable constexpr void check_rvalue_range(R& r, T const& init, F f, Expected const& expected) { - { - auto r2 = r; - is_dangling_with 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 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 decltype(auto) result = fold_left(std::move(r2), init, f); + std::same_as decltype(auto) result = std::ranges::fold_left(std::move(r2), init, f); assert(result == expected); } @@ -215,7 +117,7 @@ constexpr void check_rvalue_range(R& r, T const& init, F f, Expected const& expe auto telemetry = invocable_telemetry(); auto f2 = invocable_with_telemetry(f, telemetry); auto r2 = r; - std::same_as decltype(auto) result = fold_left(std::move(r2), init, f2); + std::same_as 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); @@ -231,29 +133,19 @@ constexpr void check(R r, T const& init, F f, Expected const& expected) { check_rvalue_range(r, init, f, expected); } -template - requires std::copyable -constexpr void check(R r, F f, std::optional const& expected) { - check_iterator(r, f, expected); -} - constexpr void empty_range_test_case() { auto const data = std::vector{}; check(data, 100, std::plus(), 100); check(data, -100, std::multiplies(), -100); - check(data, std::plus(), std::optional()); 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()); } constexpr void common_range_test_case() { auto const data = std::vector{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; @@ -261,7 +153,6 @@ constexpr void common_range_test_case() { return static_cast(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; @@ -293,7 +184,6 @@ constexpr void non_common_range_test_case() { auto data = std::vector{"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))); } { @@ -305,7 +195,6 @@ 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))); } } @@ -327,32 +216,13 @@ void runtime_only_test_case() { { auto input = std::istringstream(raw_data); auto data = std::views::istream(input); - is_in_value_result, 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(input); - is_in_value_result, 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(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(input); - assert(fold_left(data, init, std::plus()) == expected); + assert(std::ranges::fold_left(data, init, std::plus()) == expected); } } #endif diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first.pass.cpp new file mode 100644 index 0000000000000..e0a666b11c426 --- /dev/null +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first.pass.cpp @@ -0,0 +1,162 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// + +// template S, +// indirectly-binary-left-foldable F> +// requires constructible_from, iter_reference_t> +// constexpr see below fold_left_first(I first, S last, F f); // since C++23 + +// template> F> +// requires constructible_from, range_reference_t> +// constexpr see below fold_left_first(R&& r, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" +#include "test_range.h" +#include "invocable_with_telemetry.h" +#include "maths.h" + +template +concept is_in_value_result = + std::same_as, T>>; + +struct Integer { + int value; + + constexpr Integer(int const x) : value(x) {} + + constexpr Integer plus(int const x) const { return Integer{value + x}; } + + friend constexpr bool operator==(Integer const& x, Integer const& y) = default; +}; + +template + requires std::copyable +constexpr void check_iterator(R& r, F f, std::optional const& expected) { + { + std::same_as> decltype(auto) result = std::ranges::fold_left_first(r.begin(), r.end(), f); + assert(result == expected); + } + + { + std::same_as> decltype(auto) result = std::ranges::fold_left_first(r, f); + assert(result == expected); + } + + { + auto telemetry = invocable_telemetry(); + auto f2 = invocable_with_telemetry(f, telemetry); + std::same_as> decltype(auto) result = std::ranges::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); + } + } + + { + auto telemetry = invocable_telemetry(); + auto f2 = invocable_with_telemetry(f, telemetry); + std::same_as> decltype(auto) result = std::ranges::fold_left_first(r, 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 + requires std::copyable +constexpr void check(R r, F f, std::optional const& expected) { + check_iterator(r, f, expected); +} + +constexpr void empty_range_test_case() { + auto const data = std::vector{}; + check(data, std::plus(), std::optional()); + check(data | std::views::take_while([](auto) { return false; }), std::plus(), std::optional()); +} + +constexpr void common_range_test_case() { + auto const data = std::vector{1, 2, 3, 4}; + 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(result); + }; + check(data, multiply_with_prev, std::optional(factorial(data.size()) * factorial(data.size() - 1))); +} + +constexpr void non_common_range_test_case() { + auto parse = [](std::string_view const s) { + return s == "zero" ? 0.0 + : s == "one" ? 1.0 + : s == "two" ? 2.0 + : s == "three" ? 3.0 + : s == "four" ? 4.0 + : s == "five" ? 5.0 + : s == "six" ? 6.0 + : s == "seven" ? 7.0 + : s == "eight" ? 8.0 + : s == "nine" ? 9.0 + : (assert(false), 10.0); // the number here is arbitrary + }; + + { + auto data = std::vector{"five", "three", "two", "six", "one", "four"}; + auto range = data | std::views::transform(parse); + check(range, std::plus(), std::optional(triangular_sum(range))); + } + + { + auto data = std::string("five three two six one four"); + auto to_string_view = [](auto&& r) { + auto const n = std::ranges::distance(r); + return std::string_view(&*r.begin(), n); + }; + auto range = + std::views::lazy_split(data, ' ') | std::views::transform(to_string_view) | std::views::transform(parse); + check(range, std::plus(), std::optional(triangular_sum(range))); + } +} + +constexpr bool test_case() { + empty_range_test_case(); + common_range_test_case(); + non_common_range_test_case(); + return true; +} + +int main(int, char**) { + test_case(); + static_assert(test_case()); + return 0; +} diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first_with_iter.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first_with_iter.pass.cpp new file mode 100644 index 0000000000000..9eed14c0039d9 --- /dev/null +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first_with_iter.pass.cpp @@ -0,0 +1,170 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// + +// template S, +// indirectly-binary-left-foldable F> +// requires constructible_from, iter_reference_t> +// constexpr see below fold_left_first_with_iter(I first, S last, F f); // since C++23 + +// template> F> +// requires constructible_from, range_reference_t> +// constexpr see below fold_left_first_with_iter(R&& r, 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" +#include "test_range.h" +#include "invocable_with_telemetry.h" +#include "maths.h" + +template +concept is_in_value_result = + std::same_as, T>>; + +struct Integer { + int value; + + constexpr Integer(int const x) : value(x) {} + + constexpr Integer plus(int const x) const { return Integer{value + x}; } + + friend constexpr bool operator==(Integer const& x, Integer const& y) = default; +}; + +template + requires std::copyable +constexpr void check_iterator(R& r, F f, std::optional const& expected) { + { + is_in_value_result> decltype(auto) result = + std::ranges::fold_left_first_with_iter(r.begin(), r.end(), f); + assert(result.in == r.end()); + assert(result.value == expected); + } + + { + is_in_value_result> decltype(auto) result = + std::ranges::fold_left_first_with_iter(r, 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> decltype(auto) result = + std::ranges::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); + } + } + + { + auto telemetry = invocable_telemetry(); + auto f2 = invocable_with_telemetry(f, telemetry); + is_in_value_result> decltype(auto) result = + std::ranges::fold_left_first_with_iter(r, 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); + } + } +} + +template + requires std::copyable +constexpr void check(R r, F f, std::optional const& expected) { + check_iterator(r, f, expected); +} + +constexpr void empty_range_test_case() { + auto const data = std::vector{}; + check(data, std::plus(), std::optional()); + check(data | std::views::take_while([](auto) { return false; }), std::plus(), std::optional()); +} + +constexpr void common_range_test_case() { + auto const data = std::vector{1, 2, 3, 4}; + 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(result); + }; + check(data, multiply_with_prev, std::optional(factorial(data.size()) * factorial(data.size() - 1))); +} + +constexpr void non_common_range_test_case() { + auto parse = [](std::string_view const s) { + return s == "zero" ? 0.0 + : s == "one" ? 1.0 + : s == "two" ? 2.0 + : s == "three" ? 3.0 + : s == "four" ? 4.0 + : s == "five" ? 5.0 + : s == "six" ? 6.0 + : s == "seven" ? 7.0 + : s == "eight" ? 8.0 + : s == "nine" ? 9.0 + : (assert(false), 10.0); // the number here is arbitrary + }; + + { + auto data = std::vector{"five", "three", "two", "six", "one", "four"}; + auto range = data | std::views::transform(parse); + check(range, std::plus(), std::optional(triangular_sum(range))); + } + + { + auto data = std::string("five three two six one four"); + auto to_string_view = [](auto&& r) { + auto const n = std::ranges::distance(r); + return std::string_view(&*r.begin(), n); + }; + auto range = + std::views::lazy_split(data, ' ') | std::views::transform(to_string_view) | std::views::transform(parse); + check(range, std::plus(), std::optional(triangular_sum(range))); + } +} + +constexpr bool test_case() { + empty_range_test_case(); + common_range_test_case(); + non_common_range_test_case(); + return true; +} + +int main(int, char**) { + test_case(); + static_assert(test_case()); + return 0; +} diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_with_iter.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_with_iter.pass.cpp new file mode 100644 index 0000000000000..dd8663a512520 --- /dev/null +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_with_iter.pass.cpp @@ -0,0 +1,253 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +// + +// template S, class T, +// indirectly-binary-left-foldable F> +// constexpr see below fold_left_with_iter(I first, S last, T init, F f); // since C++23 + +// template> F> +// constexpr see below fold_left_with_iter(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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "test_macros.h" +#include "test_range.h" +#include "invocable_with_telemetry.h" +#include "maths.h" + +#if !defined(TEST_HAS_NO_LOCALIZATION) +# include +#endif + +template +concept is_in_value_result = + std::same_as, T>>; + +template +concept is_dangling_with = std::same_as>; + +struct Integer { + int value; + + constexpr Integer(int const x) : value(x) {} + + constexpr Integer plus(int const x) const { return Integer{value + x}; } + + friend constexpr bool operator==(Integer const& x, Integer const& y) = default; +}; + +template + requires std::copyable +constexpr void check_iterator(R& r, T const& init, F f, Expected const& expected) { + { + is_in_value_result decltype(auto) result = + std::ranges::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 decltype(auto) result = + std::ranges::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); + } +} + +template + requires std::copyable +constexpr void check_lvalue_range(R& r, T const& init, F f, Expected const& expected) { + is_in_value_result decltype(auto) result = std::ranges::fold_left_with_iter(r, init, f); + assert(result.in == r.end()); + assert(result.value == expected); +} + +template + requires std::copyable +constexpr void check_rvalue_range(R& r, T const& init, F f, Expected const& expected) { + { + auto r2 = r; + is_dangling_with decltype(auto) result = std::ranges::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 decltype(auto) result = std::ranges::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); + } +} + +template + requires std::copyable +constexpr void check(R r, T const& init, F f, Expected const& expected) { + check_iterator(r, init, f, expected); + check_lvalue_range(r, init, f, expected); + check_rvalue_range(r, init, f, expected); +} + +constexpr void empty_range_test_case() { + auto const data = std::vector{}; + check(data, 100, std::plus(), 100); + check(data, -100, std::multiplies(), -100); + + check(data | std::views::take_while([](auto) { return false; }), 1.23, std::plus(), 1.23); + check(data, Integer(52), &Integer::plus, Integer(52)); +} + +constexpr void common_range_test_case() { + auto const data = std::vector{1, 2, 3, 4}; + check(data, 0, std::plus(), triangular_sum(data)); + check(data, 1, std::multiplies(), 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(result); + }; + check(data, 1, multiply_with_prev, factorial(data.size()) * factorial(data.size() - 1)); + + auto fib = [n = 1](auto x, auto) mutable { + auto old_x = x; + x += n; + n = old_x; + return x; + }; + check(data, 0, fib, fibonacci(data.back())); + + check(data, Integer(0), &Integer::plus, Integer(triangular_sum(data))); +} + +constexpr void non_common_range_test_case() { + auto parse = [](std::string_view const s) { + return s == "zero" ? 0.0 + : s == "one" ? 1.0 + : s == "two" ? 2.0 + : s == "three" ? 3.0 + : s == "four" ? 4.0 + : s == "five" ? 5.0 + : s == "six" ? 6.0 + : s == "seven" ? 7.0 + : s == "eight" ? 8.0 + : s == "nine" ? 9.0 + : (assert(false), 10.0); // the number here is arbitrary + }; + + { + auto data = std::vector{"five", "three", "two", "six", "one", "four"}; + auto range = data | std::views::transform(parse); + check(range, 0, std::plus(), triangular_sum(range)); + } + + { + auto data = std::string("five three two six one four"); + auto to_string_view = [](auto&& r) { + auto const n = std::ranges::distance(r); + return std::string_view(&*r.begin(), n); + }; + 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)); + } +} + +constexpr bool test_case() { + empty_range_test_case(); + common_range_test_case(); + non_common_range_test_case(); + return true; +} + +// Most containers aren't constexpr +void runtime_only_test_case() { +#if !defined(TEST_HAS_NO_LOCALIZATION) + { // istream_view is a genuine input range and needs specific handling. + constexpr auto raw_data = "Shells Orange Syrup Baratie Cocoyashi Loguetown"; + constexpr auto expected = "WindmillShellsOrangeSyrupBaratieCocoyashiLoguetown"; + auto const init = std::string("Windmill"); + + { + auto input = std::istringstream(raw_data); + auto data = std::views::istream(input); + is_in_value_result, std::string> decltype(auto) result = + std::ranges::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(input); + is_in_value_result, std::string> decltype(auto) result = + std::ranges::fold_left_with_iter(data, init, std::plus()); + assert(result.in == data.end()); + assert(result.value == expected); + } + } +#endif + { + auto const data = std::forward_list{1, 3, 5, 7, 9}; + auto const n = std::ranges::distance(data); + auto const expected = static_cast(n * n); // sum of n consecutive odd numbers = n^2 + check(data, 0.0f, std::plus(), expected); + } + + { + auto const data = std::list{2, 4, 6, 8, 10, 12}; + auto const expected = triangular_sum(data); + check(data, 0, std::plus(), static_cast(expected)); + } + + { + auto const data = std::deque{-1.1, -2.2, -3.3, -4.4, -5.5, -6.6}; + auto plus = [](int const x, double const y) { return x + y; }; + auto const expected = -21.6; // int( 0.0) + -1.1 = 0 + -1.1 = -1.1 + // int(- 1.1) + -2.2 = - 1 + -2.2 = -3.2 + // int(- 3.2) + -3.3 = - 3 + -3.3 = -6.3 + // int(- 6.3) + -4.4 = - 6 + -4.4 = -10.4 + // int(-10.4) + -5.5 = -10 + -5.5 = -15.5 + // int(-15.5) + -6.6 = -15 + -6.6 = -21.6. + check(data, 0.0, plus, expected); + } +} + +int main(int, char**) { + test_case(); + static_assert(test_case()); + runtime_only_test_case(); + return 0; +} From ef107b70f53d4f0839015f57913fb7d42b9f4e18 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Fri, 21 Mar 2025 17:10:22 +0800 Subject: [PATCH 13/16] Missed clang-formatting --- .../alg.fold/ranges.fold_left_first_with_iter.pass.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first_with_iter.pass.cpp b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first_with_iter.pass.cpp index 9eed14c0039d9..15f04e8d6975f 100644 --- a/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first_with_iter.pass.cpp +++ b/libcxx/test/std/algorithms/alg.nonmodifying/alg.fold/ranges.fold_left_first_with_iter.pass.cpp @@ -63,8 +63,7 @@ constexpr void check_iterator(R& r, F f, std::optional const& expected } { - is_in_value_result> decltype(auto) result = - std::ranges::fold_left_first_with_iter(r, f); + is_in_value_result> decltype(auto) result = std::ranges::fold_left_first_with_iter(r, f); assert(result.in == r.end()); assert(result.value == expected); } From d17d852856803d8fe09b2996e14b20de7ab63638 Mon Sep 17 00:00:00 2001 From: "A. Jiang" Date: Fri, 21 Mar 2025 18:41:43 +0800 Subject: [PATCH 14/16] Fixup `niebloid.compile.pass.cpp` --- .../customization.point.object/niebloid.compile.pass.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp index faa1123f9caac..df1df09b73b0d 100644 --- a/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp +++ b/libcxx/test/std/library/description/conventions/customization.point.object/niebloid.compile.pass.cpp @@ -94,8 +94,8 @@ static_assert(test(std::ranges::find_last, a, 42)); static_assert(test(std::ranges::find_last_if, a, odd)); static_assert(test(std::ranges::find_last_if_not, a, odd)); static_assert(test(std::ranges::fold_left, a, 0, std::plus())); -static_assert(test(std::ranges::fold_left_first(a, std::plus()))); -static_assert(test(std::ranges::fold_left_first_with_iter(a, std::plus()))); +static_assert(test(std::ranges::fold_left_first, a, std::plus())); +static_assert(test(std::ranges::fold_left_first_with_iter, a, std::plus())); static_assert(test(std::ranges::fold_left_with_iter, a, 0, std::plus())); #endif static_assert(test(std::ranges::for_each, a, odd)); From b79a5ec6cc71780e2414669891fff4943440adfb Mon Sep 17 00:00:00 2001 From: JCGoran Date: Wed, 1 Oct 2025 18:36:51 +0200 Subject: [PATCH 15/16] Put back status doc --- libcxx/docs/Status/Cxx23Papers.csv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libcxx/docs/Status/Cxx23Papers.csv b/libcxx/docs/Status/Cxx23Papers.csv index 3a87e64339e1f..a70dd503d5227 100644 --- a/libcxx/docs/Status/Cxx23Papers.csv +++ b/libcxx/docs/Status/Cxx23Papers.csv @@ -65,7 +65,7 @@ "`P2286R8 `__","Formatting Ranges","2022-07 (Virtual)","|Complete|","16","`#105202 `__","" "`P2291R3 `__","Add Constexpr Modifiers to Functions ``to_chars`` and ``from_chars`` for Integral Types in ```` Header","2022-07 (Virtual)","|Complete|","16","`#105204 `__","" "`P2302R4 `__","``std::ranges::contains``","2022-07 (Virtual)","|Complete|","19","`#105206 `__","" -"`P2322R6 `__","``ranges::fold``","2022-07 (Virtual)","","","`#105208 `__","" +"`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 `__","``views::cartesian_product``","2022-07 (Virtual)","","","`#105209 `__","" "`P2404R3 `__","Move-only types for ``equality_comparable_with``, ``totally_ordered_with``, and ``three_way_comparable_with``","2022-07 (Virtual)","","","`#105210 `__","" "`P2408R5 `__","Ranges iterators as inputs to non-Ranges algorithms","2022-07 (Virtual)","","","`#105211 `__","" From b182eac4cf01b7cc42870d1421fa10f536e095fc Mon Sep 17 00:00:00 2001 From: JCGoran Date: Wed, 1 Oct 2025 18:38:50 +0200 Subject: [PATCH 16/16] Revert unrelated changes --- libcxx/include/__algorithm/ranges_fold.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libcxx/include/__algorithm/ranges_fold.h b/libcxx/include/__algorithm/ranges_fold.h index 6fb6e2319aaf8..acec3dc9e341b 100644 --- a/libcxx/include/__algorithm/ranges_fold.h +++ b/libcxx/include/__algorithm/ranges_fold.h @@ -86,12 +86,14 @@ 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>>; - if (__first == __last) + if (__first == __last) { 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)}; }