1 | //===----------------------------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | // <algorithm> |
10 | |
11 | // UNSUPPORTED: c++03, c++11, c++14, c++17, c++20 |
12 | |
13 | // MSVC warning C4244: 'argument': conversion from 'double' to 'const int', possible loss of data |
14 | // ADDITIONAL_COMPILE_FLAGS(cl-style-warnings): /wd4244 |
15 | |
16 | // template<input_iterator I, sentinel_for<I> S, class T, |
17 | // indirectly-binary-left-foldable<T, I> F> |
18 | // constexpr see below ranges::fold_left_with_iter(I first, S last, T init, F f); |
19 | // |
20 | // template<input_range R, class T, indirectly-binary-left-foldable<T, iterator_t<R>> F> |
21 | // constexpr see below ranges::fold_left_with_iter(R&& r, T init, F f); |
22 | |
23 | // template<input_iterator I, sentinel_for<I> S, class T, |
24 | // indirectly-binary-left-foldable<T, I> F> |
25 | // constexpr see below ranges::fold_left(I first, S last, T init, F f); |
26 | // |
27 | // template<input_range R, class T, indirectly-binary-left-foldable<T, iterator_t<R>> F> |
28 | // constexpr see below ranges::fold_left(R&& r, T init, F f); |
29 | |
30 | #include <algorithm> |
31 | #include <cassert> |
32 | #include <concepts> |
33 | #include <deque> |
34 | #include <forward_list> |
35 | #include <functional> |
36 | #include <iterator> |
37 | #include <list> |
38 | #include <ranges> |
39 | #include <string_view> |
40 | #include <string> |
41 | #include <vector> |
42 | |
43 | #include "test_macros.h" |
44 | #include "test_range.h" |
45 | #include "invocable_with_telemetry.h" |
46 | #include "maths.h" |
47 | |
48 | #if !defined(TEST_HAS_NO_LOCALIZATION) |
49 | # include <sstream> |
50 | #endif |
51 | |
52 | using std::ranges::fold_left; |
53 | using std::ranges::fold_left_with_iter; |
54 | |
55 | template <class Result, class Range, class T> |
56 | concept is_in_value_result = |
57 | std::same_as<Result, std::ranges::fold_left_with_iter_result<std::ranges::iterator_t<Range>, T>>; |
58 | |
59 | template <class Result, class T> |
60 | concept is_dangling_with = std::same_as<Result, std::ranges::fold_left_with_iter_result<std::ranges::dangling, T>>; |
61 | |
62 | struct Integer { |
63 | int value; |
64 | |
65 | constexpr Integer(int const x) : value(x) {} |
66 | |
67 | constexpr Integer plus(int const x) const { return Integer{value + x}; } |
68 | |
69 | friend constexpr bool operator==(Integer const& x, Integer const& y) = default; |
70 | }; |
71 | |
72 | template <std::ranges::input_range R, class T, class F, std::equality_comparable Expected> |
73 | requires std::copyable<R> |
74 | constexpr void check_iterator(R& r, T const& init, F f, Expected const& expected) { |
75 | { |
76 | is_in_value_result<R, Expected> decltype(auto) result = fold_left_with_iter(r.begin(), r.end(), init, f); |
77 | assert(result.in == r.end()); |
78 | assert(result.value == expected); |
79 | } |
80 | |
81 | { |
82 | auto telemetry = invocable_telemetry(); |
83 | auto f2 = invocable_with_telemetry(f, telemetry); |
84 | is_in_value_result<R, Expected> decltype(auto) result = fold_left_with_iter(r.begin(), r.end(), init, f2); |
85 | assert(result.in == r.end()); |
86 | assert(result.value == expected); |
87 | assert(telemetry.invocations == std::ranges::distance(r)); |
88 | assert(telemetry.moves == 0); |
89 | assert(telemetry.copies == 1); |
90 | } |
91 | |
92 | { |
93 | std::same_as<Expected> decltype(auto) result = fold_left(r.begin(), r.end(), init, f); |
94 | assert(result == expected); |
95 | } |
96 | |
97 | { |
98 | auto telemetry = invocable_telemetry(); |
99 | auto f2 = invocable_with_telemetry(f, telemetry); |
100 | std::same_as<Expected> decltype(auto) result = fold_left(r.begin(), r.end(), init, f2); |
101 | assert(result == expected); |
102 | assert(telemetry.invocations == std::ranges::distance(r)); |
103 | assert(telemetry.moves == 0); |
104 | assert(telemetry.copies == 1); |
105 | } |
106 | } |
107 | |
108 | template <std::ranges::input_range R, class T, class F, std::equality_comparable Expected> |
109 | requires std::copyable<R> |
110 | constexpr void check_lvalue_range(R& r, T const& init, F f, Expected const& expected) { |
111 | { |
112 | is_in_value_result<R, Expected> decltype(auto) result = fold_left_with_iter(r, init, f); |
113 | assert(result.in == r.end()); |
114 | assert(result.value == expected); |
115 | } |
116 | |
117 | { |
118 | auto telemetry = invocable_telemetry(); |
119 | auto f2 = invocable_with_telemetry(f, telemetry); |
120 | std::same_as<Expected> decltype(auto) result = fold_left(r, init, f2); |
121 | assert(result == expected); |
122 | assert(telemetry.invocations == std::ranges::distance(r)); |
123 | assert(telemetry.moves == 0); |
124 | assert(telemetry.copies == 1); |
125 | } |
126 | |
127 | { |
128 | std::same_as<Expected> decltype(auto) result = fold_left(r, init, f); |
129 | assert(result == expected); |
130 | } |
131 | |
132 | { |
133 | auto telemetry = invocable_telemetry(); |
134 | auto f2 = invocable_with_telemetry(f, telemetry); |
135 | std::same_as<Expected> decltype(auto) result = fold_left(r, init, f2); |
136 | assert(result == expected); |
137 | assert(telemetry.invocations == std::ranges::distance(r)); |
138 | assert(telemetry.moves == 0); |
139 | assert(telemetry.copies == 1); |
140 | } |
141 | } |
142 | |
143 | template <std::ranges::input_range R, class T, class F, std::equality_comparable Expected> |
144 | requires std::copyable<R> |
145 | constexpr void check_rvalue_range(R& r, T const& init, F f, Expected const& expected) { |
146 | { |
147 | auto r2 = r; |
148 | is_dangling_with<Expected> decltype(auto) result = fold_left_with_iter(std::move(r2), init, f); |
149 | assert(result.value == expected); |
150 | } |
151 | |
152 | { |
153 | auto telemetry = invocable_telemetry(); |
154 | auto f2 = invocable_with_telemetry(f, telemetry); |
155 | auto r2 = r; |
156 | is_dangling_with<Expected> decltype(auto) result = fold_left_with_iter(std::move(r2), init, f2); |
157 | assert(result.value == expected); |
158 | assert(telemetry.invocations == std::ranges::distance(r)); |
159 | assert(telemetry.moves == 0); |
160 | assert(telemetry.copies == 1); |
161 | } |
162 | |
163 | { |
164 | auto r2 = r; |
165 | std::same_as<Expected> decltype(auto) result = fold_left(std::move(r2), init, f); |
166 | assert(result == expected); |
167 | } |
168 | |
169 | { |
170 | auto telemetry = invocable_telemetry(); |
171 | auto f2 = invocable_with_telemetry(f, telemetry); |
172 | auto r2 = r; |
173 | std::same_as<Expected> decltype(auto) result = fold_left(std::move(r2), init, f2); |
174 | assert(result == expected); |
175 | assert(telemetry.invocations == std::ranges::distance(r)); |
176 | assert(telemetry.moves == 0); |
177 | assert(telemetry.copies == 1); |
178 | } |
179 | } |
180 | |
181 | template <std::ranges::input_range R, class T, class F, std::equality_comparable Expected> |
182 | requires std::copyable<R> |
183 | constexpr void check(R r, T const& init, F f, Expected const& expected) { |
184 | check_iterator(r, init, f, expected); |
185 | check_lvalue_range(r, init, f, expected); |
186 | check_rvalue_range(r, init, f, expected); |
187 | } |
188 | |
189 | constexpr void empty_range_test_case() { |
190 | auto const data = std::vector<int>{}; |
191 | check(data, 100, std::plus(), 100); |
192 | check(data, -100, std::multiplies(), -100); |
193 | |
194 | check(data | std::views::take_while([](auto) { return false; }), 1.23, std::plus(), 1.23); |
195 | check(data, Integer(52), &Integer::plus, Integer(52)); |
196 | } |
197 | |
198 | constexpr void common_range_test_case() { |
199 | auto const data = std::vector<int>{1, 2, 3, 4}; |
200 | check(data, 0, std::plus(), triangular_sum(data)); |
201 | check(data, 1, std::multiplies(), factorial(data.back())); |
202 | |
203 | auto multiply_with_prev = [n = 1](auto const x, auto const y) mutable { |
204 | auto const result = x * y * n; |
205 | n = y; |
206 | return static_cast<std::size_t>(result); |
207 | }; |
208 | check(data, 1, multiply_with_prev, factorial(data.size()) * factorial(data.size() - 1)); |
209 | |
210 | auto fib = [n = 1](auto x, auto) mutable { |
211 | auto old_x = x; |
212 | x += n; |
213 | n = old_x; |
214 | return x; |
215 | }; |
216 | check(data, 0, fib, fibonacci(data.back())); |
217 | |
218 | check(data, Integer(0), &Integer::plus, Integer(triangular_sum(data))); |
219 | } |
220 | |
221 | constexpr void non_common_range_test_case() { |
222 | auto parse = [](std::string_view const s) { |
223 | return s == "zero" ? 0.0 |
224 | : s == "one" ? 1.0 |
225 | : s == "two" ? 2.0 |
226 | : s == "three" ? 3.0 |
227 | : s == "four" ? 4.0 |
228 | : s == "five" ? 5.0 |
229 | : s == "six" ? 6.0 |
230 | : s == "seven" ? 7.0 |
231 | : s == "eight" ? 8.0 |
232 | : s == "nine" ? 9.0 |
233 | : (assert(false), 10.0); // the number here is arbitrary |
234 | }; |
235 | |
236 | { |
237 | auto data = std::vector<std::string>{"five" , "three" , "two" , "six" , "one" , "four" }; |
238 | auto range = data | std::views::transform(parse); |
239 | check(range, 0, std::plus(), triangular_sum(range)); |
240 | } |
241 | |
242 | { |
243 | auto data = std::string("five three two six one four" ); |
244 | auto to_string_view = [](auto&& r) { |
245 | auto const n = std::ranges::distance(r); |
246 | return std::string_view(&*r.begin(), n); |
247 | }; |
248 | auto range = |
249 | std::views::lazy_split(data, ' ') | std::views::transform(to_string_view) | std::views::transform(parse); |
250 | check(range, 0, std::plus(), triangular_sum(range)); |
251 | } |
252 | } |
253 | |
254 | constexpr bool test_case() { |
255 | empty_range_test_case(); |
256 | common_range_test_case(); |
257 | non_common_range_test_case(); |
258 | return true; |
259 | } |
260 | |
261 | // Most containers aren't constexpr |
262 | void runtime_only_test_case() { |
263 | #if !defined(TEST_HAS_NO_LOCALIZATION) |
264 | { // istream_view is a genuine input range and needs specific handling. |
265 | constexpr auto raw_data = "Shells Orange Syrup Baratie Cocoyashi Loguetown" ; |
266 | constexpr auto expected = "WindmillShellsOrangeSyrupBaratieCocoyashiLoguetown" ; |
267 | auto const init = std::string("Windmill" ); |
268 | |
269 | { |
270 | auto input = std::istringstream(raw_data); |
271 | auto data = std::views::istream<std::string>(input); |
272 | is_in_value_result<std::ranges::basic_istream_view<std::string, char>, std::string> decltype(auto) result = |
273 | fold_left_with_iter(data.begin(), data.end(), init, std::plus()); |
274 | |
275 | assert(result.in == data.end()); |
276 | assert(result.value == expected); |
277 | } |
278 | |
279 | { |
280 | auto input = std::istringstream(raw_data); |
281 | auto data = std::views::istream<std::string>(input); |
282 | is_in_value_result<std::ranges::basic_istream_view<std::string, char>, std::string> decltype(auto) result = |
283 | fold_left_with_iter(data, init, std::plus()); |
284 | assert(result.in == data.end()); |
285 | assert(result.value == expected); |
286 | } |
287 | |
288 | { |
289 | auto input = std::istringstream(raw_data); |
290 | auto data = std::views::istream<std::string>(input); |
291 | assert(fold_left(data.begin(), data.end(), init, std::plus()) == expected); |
292 | } |
293 | |
294 | { |
295 | auto input = std::istringstream(raw_data); |
296 | auto data = std::views::istream<std::string>(input); |
297 | assert(fold_left(data, init, std::plus()) == expected); |
298 | } |
299 | } |
300 | #endif |
301 | { |
302 | auto const data = std::forward_list<int>{1, 3, 5, 7, 9}; |
303 | auto const n = std::ranges::distance(data); |
304 | auto const expected = static_cast<float>(n * n); // sum of n consecutive odd numbers = n^2 |
305 | check(data, 0.0f, std::plus(), expected); |
306 | } |
307 | |
308 | { |
309 | auto const data = std::list<int>{2, 4, 6, 8, 10, 12}; |
310 | auto const expected = triangular_sum(data); |
311 | check(data, 0, std::plus<long>(), static_cast<long>(expected)); |
312 | } |
313 | |
314 | { |
315 | auto const data = std::deque<double>{-1.1, -2.2, -3.3, -4.4, -5.5, -6.6}; |
316 | auto plus = [](int const x, double const y) { return x + y; }; |
317 | auto const expected = -21.6; // int( 0.0) + -1.1 = 0 + -1.1 = -1.1 |
318 | // int(- 1.1) + -2.2 = - 1 + -2.2 = -3.2 |
319 | // int(- 3.2) + -3.3 = - 3 + -3.3 = -6.3 |
320 | // int(- 6.3) + -4.4 = - 6 + -4.4 = -10.4 |
321 | // int(-10.4) + -5.5 = -10 + -5.5 = -15.5 |
322 | // int(-15.5) + -6.6 = -15 + -6.6 = -21.6. |
323 | check(data, 0.0, plus, expected); |
324 | } |
325 | } |
326 | |
327 | int main(int, char**) { |
328 | test_case(); |
329 | static_assert(test_case()); |
330 | runtime_only_test_case(); |
331 | return 0; |
332 | } |
333 | |