Stroika Library 3.0d24
 
Loading...
Searching...
No Matches
Iterable.inl
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#include <algorithm>
5#include <execution>
6#include <set>
7
8#include "Stroika/Foundation/Containers/Adapters/Adder.h"
14
15namespace Stroika::Foundation::Traversal {
16
17 constexpr bool kIterableUsesStroikaSharedPtr [[deprecated ("Since Stroika v3.0d1 - not used")]] = false;
18
19 struct [[deprecated ("Since Stroika v3.0d1")]] IterableBase {
20 template <typename SHARED_T>
21 using PtrImplementationTemplate [[deprecated ("Since Stroika v3.0d1 - use shared_ptr directly")]] = shared_ptr<SHARED_T>;
22 template <typename SHARED_T, typename... ARGS_TYPE>
23 [[deprecated ("Since Stroika v3.0d1 - use Memory::MakeSharedPtr directly")]] static shared_ptr<SHARED_T> MakeSmartPtr (ARGS_TYPE&&... args)
24 {
25 return Memory::MakeSharedPtr<SHARED_T> (forward<ARGS_TYPE> (args)...);
26 }
27 template <typename SHARED_T>
28 using enable_shared_from_this_PtrImplementationTemplate [[deprecated ("Since Stroika v3.0d1")]] = std::enable_shared_from_this<SHARED_T>;
29 };
30
31 /*
32 ********************************************************************************
33 ***************************** Iterable<T>::_IRep *******************************
34 ********************************************************************************
35 */
36 template <typename T>
37 inline size_t Iterable<T>::_IRep::size () const
38 {
39 /*
40 * Default slow/weak implementation.
41 */
42 size_t sz{};
43 if constexpr (true) {
44 // eSeq is REQUIRED here, not just the current default: '++sz' on a captured local is a data race
45 // under any parallel policy. Do not 'simplify' this to the no-policy overload.
46 this->Apply ([&sz] (const T&) { ++sz; }, Execution::SequencePolicy::eSeq);
47 }
48 else {
49 for (Iterator<T> i = MakeIterator (); i != Iterator<T>::GetEmptyIterator (); ++i, ++sz)
50 ;
51 }
52 return sz;
53 }
54 template <typename T>
55 inline bool Iterable<T>::_IRep::empty () const
56 {
57 if (auto i = MakeIterator ()) {
58 return false;
59 }
60 return true;
61 }
62 template <typename T>
70 template <typename T>
71 inline auto Iterable<T>::_IRep::Find ([[maybe_unused]] bool findFirst, const function<bool (ArgByValueType<T> item)>& that,
73 {
75 for (Iterator<T> i = MakeIterator (); i != Iterator<T>::GetEmptyIterator (); ++i) {
76 if (that (*i)) {
77 return i;
78 }
79 }
81 }
82 template <typename T>
85 {
86 if constexpr (Common::IEqualToOptimizable<T>) {
87 /*
88 * This is the default implementation. It is only ever if there is a valid equal_to<> around, and
89 * that valid equal_to<> is stateless (verified by Common::IEqualToOptimizable).
90 */
91 if constexpr (true) {
92 // simpler but not sure if faster; better though cuz by default leverages seq, which might
93 // help. In 'size' testing, on windows, this was slightly larger, so
94 // not 100% sure this is the best default -- LGP 2023-02-06
95 return Find (/*findFirst*/ true, [&v] (const T& rhs) { return equal_to<T>{}(v, rhs); }, seq);
96 }
97 else {
98 for (Iterator<T> i = MakeIterator (); i != Iterator<T>::GetEmptyIterator (); ++i) {
99 if (equal_to<T>{}(v, *i)) {
100 return i;
101 }
102 }
104 }
105 }
106 else {
107 RequireNotReached (); // cannot call if not IEqualToOptimizable
109 }
110 }
111 template <typename T>
113 {
114 return nullopt; // ie no contiguous storage to offer; callers fall back to iterating
115 }
116
117 /*
118 ********************************************************************************
119 ******************* Iterable<T>::_SafeReadRepAccessor **************************
120 ********************************************************************************
121 */
122 template <typename T>
123 template <typename REP_SUB_TYPE>
125 : fConstRef_{Debug::UncheckedDynamicCast<const REP_SUB_TYPE*> (it->_fRep.cget ())}
126 , fIterableEnvelope_{it}
127#if qStroika_Foundation_Debug_AssertionsChecked
128 , fAssertReadLock_{it->_fThisAssertExternallySynchronized}
129#endif
130 {
132 EnsureMember (fConstRef_, REP_SUB_TYPE);
133 }
134 template <typename T>
135 template <typename REP_SUB_TYPE>
136 inline Iterable<T>::_SafeReadRepAccessor<REP_SUB_TYPE>::_SafeReadRepAccessor (_SafeReadRepAccessor&& src) noexcept
137 : fConstRef_{src.fConstRef_}
138 , fIterableEnvelope_{src.fIterableEnvelope_}
139#if qStroika_Foundation_Debug_AssertionsChecked
140 , fAssertReadLock_{move (src.fAssertReadLock_)}
141#endif
142 {
143 RequireNotNull (fConstRef_);
144 EnsureMember (fConstRef_, REP_SUB_TYPE);
145 src.fConstRef_ = nullptr;
146 }
147 template <typename T>
148 template <typename REP_SUB_TYPE>
149 inline auto Iterable<T>::_SafeReadRepAccessor<REP_SUB_TYPE>::operator= (_SafeReadRepAccessor&& rhs) noexcept -> _SafeReadRepAccessor&
150 {
151 fConstRef_ = rhs.fConstRef_;
152 this->fIterableEnvelope_ = rhs.fIterableEnvelope_;
153#if qStroika_Foundation_Debug_AssertionsChecked
154 this->fAssertReadLock_ = move (rhs.fAssertReadLock_);
155#endif
156 return *this;
157 }
158 template <typename T>
159 template <typename REP_SUB_TYPE>
160 inline const REP_SUB_TYPE& Iterable<T>::_SafeReadRepAccessor<REP_SUB_TYPE>::_ConstGetRep () const noexcept
161 {
162 EnsureMember (fConstRef_, REP_SUB_TYPE);
163 return *fConstRef_;
164 }
165 template <typename T>
166 template <typename REP_SUB_TYPE>
167 inline auto Iterable<T>::_SafeReadRepAccessor<REP_SUB_TYPE>::_ConstGetRepSharedPtr () const noexcept -> shared_ptr<REP_SUB_TYPE>
168 {
169 return Debug::UncheckedDynamicPointerCast<REP_SUB_TYPE> (this->fIterableEnvelope_->_fRep.cget_ptr ());
170 }
171
172 /*
173 ********************************************************************************
174 ************* Iterable<CONTAINER_OF_T, T>::_SafeReadWriteRepAccessor ***********
175 ********************************************************************************
176 */
177 template <typename T>
178 template <typename REP_SUB_TYPE>
179 inline Iterable<T>::_SafeReadWriteRepAccessor<REP_SUB_TYPE>::_SafeReadWriteRepAccessor (Iterable<T>* iterableEnvelope)
180 : fRepReference_{Debug::UncheckedDynamicCast<REP_SUB_TYPE*> (iterableEnvelope->_fRep.rwget ())}
182 , fAssertWriteLock_{iterableEnvelope->_fThisAssertExternallySynchronized}
183 , fIterableEnvelope_{iterableEnvelope}
184#endif
185 {
187 }
188 template <typename T>
189 template <typename REP_SUB_TYPE>
190 inline Iterable<T>::_SafeReadWriteRepAccessor<REP_SUB_TYPE>::_SafeReadWriteRepAccessor (_SafeReadWriteRepAccessor&& from)
191 : fRepReference_{from.fRepReference_}
194 , fIterableEnvelope_{from.fIterableEnvelope_}
195#endif
196 {
197 RequireNotNull (fRepReference_);
198 EnsureMember (fRepReference_, REP_SUB_TYPE);
199#if qStroika_Foundation_Debug_AssertionsChecked
200 from.fIterableEnvelope_ = nullptr;
201#endif
202 from.fRepReference_ = nullptr;
203 }
204 template <typename T>
205 template <typename REP_SUB_TYPE>
206 inline auto Iterable<T>::_SafeReadWriteRepAccessor<REP_SUB_TYPE>::operator= (_SafeReadWriteRepAccessor&& rhs) noexcept -> _SafeReadWriteRepAccessor&
207 {
208 fRepReference_ = rhs.fRepReference_;
209 EnsureMember (fRepReference_, REP_SUB_TYPE);
210#if qStroika_Foundation_Debug_AssertionsChecked
211 this->fAssertWriteLock_ = move (rhs.fAssertWriteLock_);
212 this->fIterableEnvelope_ = rhs.fIterableEnvelope_;
213 rhs.fIterableEnvelope_ = nullptr;
214#endif
215 return *this;
216 }
217 template <typename T>
218 template <typename REP_SUB_TYPE>
219 inline const REP_SUB_TYPE& Iterable<T>::_SafeReadWriteRepAccessor<REP_SUB_TYPE>::_ConstGetRep () const
220 {
221 EnsureNotNull (fRepReference_);
222 return *fRepReference_;
223 }
224 template <typename T>
225 template <typename REP_SUB_TYPE>
226 inline REP_SUB_TYPE& Iterable<T>::_SafeReadWriteRepAccessor<REP_SUB_TYPE>::_GetWriteableRep ()
227 {
228 EnsureNotNull (fRepReference_);
229#if qStroika_Foundation_Debug_AssertionsChecked
230 EnsureNotNull (fIterableEnvelope_);
231 EnsureNotNull (fIterableEnvelope_->_fRep);
232 Ensure (fIterableEnvelope_->_fRep.use_count () == 1);
233#endif
234 return *fRepReference_;
235 }
236
237 /*
238 ********************************************************************************
239 ********************************** Iterable<T> *********************************
240 ********************************************************************************
241 */
242 template <typename T>
243 inline Iterable<T>::Iterable (const shared_ptr<typename Iterable<T>::_IRep>& rep) noexcept
244 : _fRep{(RequireExpression (rep != nullptr), rep)}
245 {
246 Require (_fRep.GetSharingState () != Memory::SharedByValueSupport::SharingState::eNull);
247 }
248 template <typename T>
249 inline Iterable<T>::Iterable (shared_ptr<typename Iterable<T>::_IRep>&& rep) noexcept
250 : _fRep{(RequireExpression (rep != nullptr), move (rep))}
251 {
252 Require (_fRep.GetSharingState () != Memory::SharedByValueSupport::SharingState::eNull);
253 Require (rep == nullptr); // after move (see https://en.cppreference.com/w/cpp/memory/shared_ptr/shared_ptr "After the construction, ... r is empty and its stored pointer is null"
254 }
255#if !qCompilerAndStdLib_RequiresNotMatchInlineOutOfLineForTemplateClassBeingDefined_Buggy
256 template <typename T>
257 template <IIterableOfTo<T> CONTAINER_OF_T>
264#endif
265 template <typename T>
267 : _fRep{mk_ (from)._fRep}
268 {
269 }
270 template <typename T>
271 inline Iterable<T>::operator bool () const
272 {
273 return not empty ();
274 }
275 template <typename T>
276 inline shared_ptr<typename Iterable<T>::_IRep> Iterable<T>::Clone_ (const _IRep& rep)
277 {
278 return rep.Clone ();
279 }
280 template <typename T>
281 template <typename CONTAINER_OF_T>
282 Iterable<T> Iterable<T>::mk_ (CONTAINER_OF_T&& from)
283 requires (copyable<remove_cvref_t<CONTAINER_OF_T>> or same_as<remove_cvref_t<CONTAINER_OF_T>, initializer_list<T>>)
284 {
285 using DECAYED_CONTAINER = remove_cvref_t<CONTAINER_OF_T>;
286 // Most containers are safe to use copy-by-value, except not initializer_list<> - not sure how to check for that generically...
287 using USE_CONTAINER_TYPE =
288 conditional_t<copy_constructible<DECAYED_CONTAINER> and not same_as<DECAYED_CONTAINER, initializer_list<T>>, DECAYED_CONTAINER, vector<T>>;
289 auto sharedCopyOfContainer = Memory::MakeSharedPtr<USE_CONTAINER_TYPE> (forward<CONTAINER_OF_T> (from));
290 // shared copy so if/when getNext copied, the container itself isn't (so not invalidating any iterators)
291 function<optional<T> ()> getNext = [sharedCopyOfContainer, i = sharedCopyOfContainer->begin ()] () mutable -> optional<T> {
292 if (i != sharedCopyOfContainer->end ()) {
293 return *i++; // intentionally increment AFTER returning value
294 }
295 return nullopt;
296 };
297 return CreateGenerator (getNext);
298 }
299 template <typename T>
301 {
302 return _fRep.GetSharingState ();
303 }
304 template <typename T>
306 {
307 _SafeReadRepAccessor<> accessor{this};
308 return accessor._ConstGetRep ().MakeIterator ();
309 }
310 template <typename T>
311 inline size_t Iterable<T>::size () const
312 {
313 _SafeReadRepAccessor<> accessor{this};
314 return accessor._ConstGetRep ().size ();
315 }
316 template <typename T>
317 inline bool Iterable<T>::empty () const
318 {
319 _SafeReadRepAccessor<> accessor{this};
320 return accessor._ConstGetRep ().empty ();
321 }
322 template <typename T>
323 template <Common::IPotentiallyComparer<T> EQUALS_COMPARER>
324 bool Iterable<T>::Contains (ArgByValueType<T> element, EQUALS_COMPARER&& equalsComparer) const
325 {
326 /*
327 * FAST PATH - when this backend keeps its elements contiguously, scan that buffer directly.
328 *
329 * This is worth more here than the span alone suggests, because the general path below is
330 * unusually expensive for what it computes: it wraps the comparison in a lambda, hands that to
331 * Find (), which type-erases it into a std::function, walks the container through Iterable<T>'s
332 * virtuals calling through that function per element, CONSTRUCTS an Iterator<T> at the match,
333 * and then throws the iterator away to yield a bool. The fast path does none of those.
334 *
335 * Dropping the comparer where it is the default one lets the standard library pick its
336 * vectorized find for the types it can (the same reason SequentialEquals () does it).
337 *
338 * \note This does NOT displace a better algorithm anywhere. Set, MultiSet, SortedCollection,
339 * KeyedCollection and Collection all declare their own Contains () routing to their
340 * backend's keyed lookup, so they never reach this. What is left - Sequence<T> and a
341 * plain Iterable<T> - is linear either way, so a span is a pure win.
342 */
343 {
344 _SafeReadRepAccessor<> accessor{this};
345 if (auto s = accessor._ConstGetRep ().PeekContiguousStorage ()) {
346 if constexpr (same_as<remove_cvref_t<EQUALS_COMPARER>, equal_to<T>> or same_as<remove_cvref_t<EQUALS_COMPARER>, equal_to<>>) {
347 return std::find (s->begin (), s->end (), element) != s->end ();
348 }
349 else {
350 return std::find_if (s->begin (), s->end (), [&] (const T& i) { return equalsComparer (i, element); }) != s->end ();
352 }
353 }
354 // grab iterator to first matching item, and contains if not at end; this is faster than using iterators
355 return static_cast<bool> (
356 this->Find ([&element, &equalsComparer] (ArgByValueType<T> i) -> bool { return equalsComparer (i, element); }));
357 }
358 template <typename T>
359 template <ranges::range LHS_CONTAINER_TYPE, ranges::range RHS_CONTAINER_TYPE, Common::IEqualsComparer<T> EQUALS_COMPARER>
360 bool Iterable<T>::SetEquals (const LHS_CONTAINER_TYPE& lhs, const RHS_CONTAINER_TYPE& rhs, EQUALS_COMPARER&& equalsComparer)
361 {
362 // @todo OPTIMIZATION - check if constexpr EQUALS_COMPARE == equal_to and if less<> defined - and if so - construct a std::set<> to lookup/compare (do on shorter side)
363 /*
364 * An extremely inefficient but space-constant implementation. N^2 and check
365 * a contains b and b contains a
366 */
367 for (const auto& ti : lhs) {
368 bool contained = false;
369 for (const auto& ri : rhs) {
370 if (equalsComparer (ti, ri)) {
371 contained = true;
372 break;
373 }
374 }
375 if (not contained) {
376 return false;
377 }
378 }
379 for (const auto& ri : rhs) {
380 bool contained = false;
381 for (const auto& ti : lhs) {
382 if (equalsComparer (ti, ri)) {
383 contained = true;
384 break;
385 }
386 }
387 if (not contained) {
388 return false;
389 }
390 }
391 return true;
392 }
393 template <typename T>
394 template <ranges::range RHS_CONTAINER_TYPE, Common::IEqualsComparer<T> EQUALS_COMPARER>
395 inline bool Iterable<T>::SetEquals (const RHS_CONTAINER_TYPE& rhs, EQUALS_COMPARER&& equalsComparer) const
396 {
397 return SetEquals (*this, rhs, forward<EQUALS_COMPARER> (equalsComparer));
398 }
399 template <typename T>
400 template <ranges::range LHS_CONTAINER_TYPE, ranges::range RHS_CONTAINER_TYPE, Common::IEqualsComparer<T> EQUALS_COMPARER>
401 bool Iterable<T>::MultiSetEquals (const LHS_CONTAINER_TYPE& lhs, const RHS_CONTAINER_TYPE& rhs, EQUALS_COMPARER&& equalsComparer)
402 {
403 auto tallyOf = [&equalsComparer] (const auto& c, ArgByValueType<T> item) -> size_t {
404 size_t total = 0;
405 for (const auto& ti : c) {
406 if (equalsComparer (ti, item)) {
407 ++total;
408 }
409 }
410 return total;
411 };
412 /*
413 * An extremely in-efficient but space-constant implementation. N^3 and check
414 * a contains b and b contains a
415 */
416 for (const auto& ti : lhs) {
417 if (tallyOf (lhs, ti) != tallyOf (rhs, ti)) {
418 return false;
419 }
420 }
421 for (const auto& ti : rhs) {
422 if (tallyOf (lhs, ti) != tallyOf (rhs, ti)) {
423 return false;
424 }
425 }
426 return true;
427 }
428 template <typename T>
429 template <ranges::range RHS_CONTAINER_TYPE, Common::IEqualsComparer<T> EQUALS_COMPARER>
430 inline bool Iterable<T>::MultiSetEquals (const RHS_CONTAINER_TYPE& rhs, EQUALS_COMPARER&& equalsComparer) const
431 {
432 return MultiSetEquals (*this, rhs, equalsComparer);
433 }
434 template <typename T>
435 template <ranges::range LHS_CONTAINER_TYPE, ranges::range RHS_CONTAINER_TYPE, Common::IEqualsComparer<T> EQUALS_COMPARER>
436 bool Iterable<T>::SequentialEquals (const LHS_CONTAINER_TYPE& lhs, const RHS_CONTAINER_TYPE& rhs, EQUALS_COMPARER&& equalsComparer)
437 {
438 /*
439 * FAST PATH - when BOTH sides can be seen as contiguous runs of T, compare them as spans instead of
440 * advancing two Stroika iterators in lockstep. Per-element virtual iteration measures ~6ns for int
441 * and ~12ns for String ('Test52 --show --orderby-probe', the "non-copying consumer shape" entries);
442 * a span walk pays none of it, which is a 46x/13x saving on a comparison that copies nothing. And
443 * where T is trivially comparable, dropping the predicate lets the standard library collapse the
444 * whole thing to a memcmp.
445 *
446 * A side qualifies two ways, and they are not the same mechanism:
447 * o an Iterable<T> (or subclass) whose backend offers _IRep::PeekContiguousStorage ()
448 * o any OTHER contiguous_range of T - vector<T>, array<T,N>, span<T>, and notably
449 * initializer_list<T>, which is the default RHS_CONTAINER_TYPE, so
450 * 'c.SequentialEquals ({1, 2, 3})' takes this path
451 * Only the first needs a _SafeReadRepAccessor; a plain range's data () is not COW-managed. Each
452 * accessor is a named local in the scope that uses the span, per PeekContiguousStorage ()'s
453 * precondition - which is also why this cannot be factored into a helper returning just the span.
454 *
455 * useIterableSize is irrelevant here: comparing span sizes is O(1) either way, and "same length and
456 * same elements" is exactly what the general path below computes. So this deliberately does NOT
457 * call size () on either container, which is what that flag exists to let callers avoid.
458 */
459 {
460 using LHS_ = remove_cvref_t<LHS_CONTAINER_TYPE>;
461 using RHS_ = remove_cvref_t<RHS_CONTAINER_TYPE>;
462 constexpr bool kLHSIsIterable_ = derived_from<LHS_, Iterable<T>>;
463 constexpr bool kRHSIsIterable_ = derived_from<RHS_, Iterable<T>>;
464 constexpr bool kLHSIsContiguous_ =
465 not kLHSIsIterable_ and ranges::contiguous_range<LHS_> and same_as<remove_cvref_t<ranges::range_value_t<LHS_>>, T>;
466 constexpr bool kRHSIsContiguous_ =
467 not kRHSIsIterable_ and ranges::contiguous_range<RHS_> and same_as<remove_cvref_t<ranges::range_value_t<RHS_>>, T>;
468 if constexpr ((kLHSIsIterable_ or kLHSIsContiguous_) and (kRHSIsIterable_ or kRHSIsContiguous_)) {
469 auto spansEqual_ = [&] (span<const T> l, span<const T> r) -> bool {
470 if (l.size () != r.size ()) {
471 return false;
472 }
473 // Passing NO predicate where the comparer is the default one is what licenses the
474 // library's memcmp specialization; equal_to<T> and operator== agree by definition.
475 if constexpr (same_as<remove_cvref_t<EQUALS_COMPARER>, equal_to<T>> or same_as<remove_cvref_t<EQUALS_COMPARER>, equal_to<>>) {
476 return std::equal (l.begin (), l.end (), r.begin ());
478 else {
479 return std::equal (l.begin (), l.end (), r.begin (), equalsComparer);
480 }
481 };
482 // Completes the comparison once the LHS span is in hand; nullopt = RHS had no storage to
483 // offer, so fall through to the general path below.
484 auto withLHSSpan_ = [&] (span<const T> l) -> optional<bool> {
485 if constexpr (kRHSIsIterable_) {
486 _SafeReadRepAccessor<> rhsAccessor{&rhs};
487 if (auto rs = rhsAccessor._ConstGetRep ().PeekContiguousStorage ()) {
488 return spansEqual_ (l, *rs);
489 }
490 return nullopt;
491 }
492 else {
493 return spansEqual_ (l, span<const T>{ranges::data (rhs), ranges::size (rhs)});
494 }
495 };
496 if constexpr (kLHSIsIterable_) {
497 _SafeReadRepAccessor<> lhsAccessor{&lhs};
498 if (auto ls = lhsAccessor._ConstGetRep ().PeekContiguousStorage ()) {
499 if (auto result = withLHSSpan_ (*ls)) {
500 return *result;
501 }
502 }
503 }
504 else {
505 if (auto result = withLHSSpan_ (span<const T>{ranges::data (lhs), ranges::size (lhs)})) {
506 return *result;
507 }
508 }
509 }
511 auto li{lhs.begin ()};
512 auto ri{rhs.begin ()};
513 auto le{lhs.end ()};
514 auto re{rhs.end ()};
515 for (; li != le and ri != re; ++ri, ++li) {
516 if (not equalsComparer (*li, *ri)) {
517 return false;
518 }
519 }
520 // one caused us to end (or more likely both)
521 Assert (li == le or ri == re);
522 // only true if we get to end at the same time
523 return li == le and ri == re;
524 }
525 template <typename T>
526 template <ranges::range RHS_CONTAINER_TYPE, Common::IEqualsComparer<T> EQUALS_COMPARER>
527 inline bool Iterable<T>::SequentialEquals (const RHS_CONTAINER_TYPE& rhs, EQUALS_COMPARER&& equalsComparer) const
528 {
529 return SequentialEquals (*this, rhs, forward<EQUALS_COMPARER> (equalsComparer));
530 }
531 DISABLE_COMPILER_MSC_WARNING_START (4996)
532 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdeprecated-declarations\"")
533 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
534 template <typename T>
535 template <ranges::range LHS_CONTAINER_TYPE, ranges::range RHS_CONTAINER_TYPE, Common::IEqualsComparer<T> EQUALS_COMPARER>
536 inline bool Iterable<T>::SequentialEquals (const LHS_CONTAINER_TYPE& lhs, const RHS_CONTAINER_TYPE& rhs,
537 EQUALS_COMPARER&& equalsComparer, [[maybe_unused]] bool useIterableSize)
538 {
539 // useIterableSize deliberately ignored - it never changed the ANSWER, only the strategy, and the
540 // strategy it selected is not worth having (see the \deprecated note in Iterable.h).
541 return SequentialEquals (lhs, rhs, forward<EQUALS_COMPARER> (equalsComparer));
542 }
543 template <typename T>
544 template <ranges::range RHS_CONTAINER_TYPE, Common::IEqualsComparer<T> EQUALS_COMPARER>
545 inline bool Iterable<T>::SequentialEquals (const RHS_CONTAINER_TYPE& rhs, EQUALS_COMPARER&& equalsComparer, [[maybe_unused]] bool useIterableSize) const
546 {
547 return SequentialEquals (*this, rhs, forward<EQUALS_COMPARER> (equalsComparer));
548 }
549 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
550 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wdeprecated-declarations\"")
551 DISABLE_COMPILER_MSC_WARNING_END (4996)
552 template <typename T>
553#if qCompilerAndStdLib_RequiresNotMatchInlineOutOfLineForTemplateClassBeingDefined_Buggy
554 template <typename RESULT_CONTAINER, predicate<T> INCLUDE_PREDICATE>
555#else
556 template <derived_from<Iterable<T>> RESULT_CONTAINER, predicate<T> INCLUDE_PREDICATE>
557#endif
558 inline RESULT_CONTAINER Iterable<T>::Where (INCLUDE_PREDICATE&& includeIfTrue) const
559 {
560 //
561 // LAZY evaluate Iterable<> and for concrete container types, explicitly create the object.
562 //
563 if constexpr (same_as<RESULT_CONTAINER, Iterable<T>>) {
564 // If we have many iterator copies, we need ONE copy of this sharedContext (they all share a reference to the same Iterable)
565 auto sharedContext = Memory::MakeSharedPtr<Iterable<T>> (*this);
566 // If we have many iterator copies, each needs to copy their 'base iterator' (this is their 'index' into the container)
567 // Both the 'sharedContext' and the i' get stored into the lambda closure so they get appropriately copied as you copy iterators
568 function<optional<T> ()> getNext = [sharedContext, i = sharedContext->MakeIterator (), includeIfTrue] () mutable -> optional<T> {
569 while (i and not includeIfTrue (*i)) {
570 ++i;
571 }
572 if (i) {
573 auto tmp = *i;
574 ++i;
575 return move (tmp);
576 }
577 return nullopt;
578 };
579 return CreateGenerator (getNext);
580 }
581 else {
582 return Where<RESULT_CONTAINER> (forward<INCLUDE_PREDICATE> (includeIfTrue), RESULT_CONTAINER{});
583 }
584 }
585 template <typename T>
586#if qCompilerAndStdLib_RequiresNotMatchInlineOutOfLineForTemplateClassBeingDefined_Buggy
587 template <typename RESULT_CONTAINER, predicate<T> INCLUDE_PREDICATE>
588#else
589 template <derived_from<Iterable<T>> RESULT_CONTAINER, predicate<T> INCLUDE_PREDICATE>
590#endif
591 RESULT_CONTAINER Iterable<T>::Where (INCLUDE_PREDICATE&& includeIfTrue, [[maybe_unused]] RESULT_CONTAINER&& emptyResult) const
592 {
593 if constexpr (same_as<RESULT_CONTAINER, Iterable<T>>) {
594 // no point in emptyResult overload; vector to one spot for Iterable<> lazy implementation
595 return Where<RESULT_CONTAINER> (forward<INCLUDE_PREDICATE> (includeIfTrue));
596 }
597 else {
598 Require (emptyResult.empty ());
599 RESULT_CONTAINER result = forward<RESULT_CONTAINER> (emptyResult);
600 this->Apply ([&result, &includeIfTrue] (ArgByValueType<T> arg) {
601 if (includeIfTrue (arg)) {
603 }
604 });
605 return result;
606 }
607 }
608 template <typename T>
609 template <Common::IPotentiallyComparer<T> EQUALS_COMPARER>
610 Iterable<T> Iterable<T>::Distinct (EQUALS_COMPARER&& equalsComparer) const
611 {
612 vector<T> tmp; // Simplistic/stupid/weak implementation
613 if constexpr (same_as<equal_to<T>, EQUALS_COMPARER> and is_invocable_v<less<T>>) {
614 set<T> t1{begin (), end ()};
615 tmp = vector<T>{t1.begin (), t1.end ()};
616 }
617 else {
618 for (const auto& i : *this) {
619 if (find_if (tmp.begin (), tmp.end (), [&] (ArgByValueType<T> n) { return equalsComparer (n, i); }) == tmp.end ()) {
620 tmp.push_back (i);
621 }
622 }
623 }
624 function<optional<T> ()> getNext = [container = move (tmp), idx = size_t{0}] () mutable -> optional<T> {
625 if (idx < container.size ()) {
626 return container[idx++];
627 }
628 else {
629 return nullopt;
630 }
631 };
632 return CreateGenerator (getNext);
633 }
634 template <typename T>
635 template <typename RESULT, Common::IPotentiallyComparer<T> EQUALS_COMPARER>
636 Iterable<RESULT> Iterable<T>::Distinct (const function<RESULT (ArgByValueType<T>)>& extractElt, EQUALS_COMPARER&& equalsComparer) const
637 {
638 RequireNotNull (extractElt);
639 vector<RESULT> tmp; // Simplistic/stupid/weak implementation
640 if constexpr (same_as<equal_to<T>, EQUALS_COMPARER> and is_invocable_v<less<T>>) {
641 set<RESULT> t1;
642 for (const T& i : *this) {
643 t1.add (extractElt (i));
644 }
645 tmp = vector<RESULT>{t1.begin (), t1.end ()};
646 }
647 else {
648 for (const T& i : *this) {
649 RESULT item2Test = extractElt (i);
650 if (find_if (tmp.begin (), tmp.end (), [&] (ArgByValueType<T> n) { return equalsComparer (n, item2Test); }) == tmp.end ()) {
651 tmp.push_back (item2Test);
652 }
653 }
654 }
655 function<optional<RESULT> ()> getNext = [container = move (tmp), idx = size_t{0}] () mutable -> optional<RESULT> {
656 if (idx < container.size ()) {
657 return container[idx++];
658 }
659 else {
660 return nullopt;
661 }
662 };
663 return CreateGenerator (getNext);
664 }
665 template <typename T>
666 template <ranges::range RESULT_CONTAINER, invocable<T> ELEMENT_MAPPER>
667 RESULT_CONTAINER Iterable<T>::Map (ELEMENT_MAPPER&& elementMapper) const
668 requires (convertible_to<invoke_result_t<ELEMENT_MAPPER, T>, typename RESULT_CONTAINER::value_type> or
669 convertible_to<invoke_result_t<ELEMENT_MAPPER, T>, optional<typename RESULT_CONTAINER::value_type>>)
670 {
671 using RESULT_ELEMENT = typename RESULT_CONTAINER::value_type;
672 constexpr bool kLazyEvaluateIteration_ = same_as<RESULT_CONTAINER, Iterable<RESULT_ELEMENT>>; // For now use vector and lazy not truly implemented
673 [[maybe_unused]] constexpr bool kOptionalExtractor_ =
674 not convertible_to<invoke_result_t<ELEMENT_MAPPER, T>, typename RESULT_CONTAINER::value_type> and
675 convertible_to<invoke_result_t<ELEMENT_MAPPER, T>, optional<typename RESULT_CONTAINER::value_type>>;
676 if constexpr (kLazyEvaluateIteration_) {
677 // If we have many iterator copies, we need ONE copy of this sharedContext (they all share a reference to the same Iterable)
678 auto sharedContext = Memory::MakeSharedPtr<Iterable<T>> (*this);
679 // Both the 'sharedContext' and the 'i' get stored into the lambda closure so they get appropriately copied as you copy iterators
680 function<optional<RESULT_ELEMENT> ()> getNext = [sharedContext, i = sharedContext->MakeIterator (),
681 elementMapper] () mutable -> optional<RESULT_ELEMENT> {
682 // tricky. The function we are defining returns nullopt as a sentinel to signal end of iteration. The function we are GIVEN returns nullopt
683 // to signal skip this item. So adjust accordingly
684 if constexpr (kOptionalExtractor_) {
685 while (i) {
686 optional<RESULT_ELEMENT> t = elementMapper (*i);
687 ++i;
688 if (t) {
689 return *t;
690 }
691 }
692 return nullopt;
693 }
694 else {
695 if (i) {
696 RESULT_ELEMENT result = elementMapper (*i);
697 ++i;
698 return move (result);
699 }
700 return nullopt;
701 }
702 };
703 return CreateGenerator (getNext);
704 }
705 else {
706 // subclasseers can replace this 'else' branch if RESULT_CONTAINER cannot or should not be default constructed
707 return this->Map<RESULT_CONTAINER> (forward<ELEMENT_MAPPER> (elementMapper), RESULT_CONTAINER{});
708 }
709 }
710 template <typename T>
711 template <ranges::range RESULT_CONTAINER, invocable<T> ELEMENT_MAPPER>
712 RESULT_CONTAINER Iterable<T>::Map (ELEMENT_MAPPER&& elementMapper, RESULT_CONTAINER&& emptyResult) const
713 requires (convertible_to<invoke_result_t<ELEMENT_MAPPER, T>, typename RESULT_CONTAINER::value_type> or
714 convertible_to<invoke_result_t<ELEMENT_MAPPER, T>, optional<typename RESULT_CONTAINER::value_type>>)
715 {
716 using RESULT_ELEMENT = typename RESULT_CONTAINER::value_type;
717 constexpr bool kLazyEvaluateIteration_ = same_as<RESULT_CONTAINER, Iterable<RESULT_ELEMENT>>; // For now use vector and lazy not truly implemented
718 constexpr bool kOptionalExtractor_ = not convertible_to<invoke_result_t<ELEMENT_MAPPER, T>, typename RESULT_CONTAINER::value_type> and
719 convertible_to<invoke_result_t<ELEMENT_MAPPER, T>, optional<typename RESULT_CONTAINER::value_type>>;
720 if constexpr (kLazyEvaluateIteration_) {
721 return this->Map<RESULT_CONTAINER> (forward<ELEMENT_MAPPER> (elementMapper)); // ignore useless emptyResult if provided, and invoke other overload to share code
722 }
723 else {
724 RESULT_CONTAINER c = forward<RESULT_CONTAINER> (emptyResult);
725 /*
726 * \note BUG - see https://github.com/SophistSolutions/Stroika/issues/1163
727 *
728 * This reserves iff the TARGET supports reserve () and we are not filtering - it does
729 * NOT establish that our own size () is cheap, despite what this comment used to say.
730 * _IRep::size () defaults to counting by iteration, so for a lazy source (Where ()
731 * returning Iterable<T> is a generator closure) this walks everything for the size and
732 * then Apply () walks it all again. Performance only - generator Iterables are
733 * re-traversable, so the result is correct, just computed twice.
734 *
735 * Not reachable from in-tree code today: no Stroika container has reserve (), and no
736 * in-tree caller does Map<vector<...>> (). It bites user code mapping into a
737 * std::vector. The clean fix wants a PeekSize () - declined for now, see issue #1162.
738 */
739 if constexpr (not kOptionalExtractor_ and requires (RESULT_CONTAINER p) { p.reserve (3u); }) {
740 c.reserve (this->size ());
741 }
742 this->Apply ([&c, &elementMapper] (ArgByValueType<T> arg) {
743 if constexpr (kOptionalExtractor_) {
744 if (auto oarg = elementMapper (arg)) {
745 Containers::Adapters::Adder<RESULT_CONTAINER>::Add (&c, *oarg);
746 }
747 }
748 else {
749 Containers::Adapters::Adder<RESULT_CONTAINER>::Add (&c, elementMapper (arg));
750 }
751 });
752 return c;
753 }
754 }
755 template <typename T>
756 template <typename RESULT_T, invocable<T> CONVERT_TO_RESULT, invocable<RESULT_T, RESULT_T, bool> COMBINER>
757 RESULT_T Iterable<T>::Join (const CONVERT_TO_RESULT& convertToResult, const COMBINER& combiner) const
758 requires (convertible_to<invoke_result_t<CONVERT_TO_RESULT, T>, RESULT_T> and
759 convertible_to<invoke_result_t<COMBINER, RESULT_T, RESULT_T, bool>, RESULT_T>)
760 {
761 RESULT_T result{};
762 size_t idx{0};
763 size_t cnt = this->size ();
764 for (auto i : *this) {
765 if (idx == 0) {
766 result = convertToResult (i);
767 }
768 else {
769 result = combiner (result, convertToResult (i), idx + 1 == cnt);
770 }
771 ++idx;
772 }
773 return result;
774 }
775 template <typename T>
776 Iterable<T> Iterable<T>::Skip (size_t nItems) const
777 {
778 // If we have many iterator copies, we need ONE copy of this sharedContext (they all share a reference to the same Iterable)
779 auto sharedContext = Memory::MakeSharedPtr<Iterable<T>> (*this);
780 // If we have many iterator copies, each needs to copy their 'base iterator' (this is their 'index' into the container)
781 // Both the 'sharedContext' and the i' get stored into the lambda closure so they get appropriately copied as you copy iterators
782 // perIteratorContextNItemsToSkip also must be cloned per iterator instance
783 function<optional<T> ()> getNext = [sharedContext, i = sharedContext->MakeIterator (),
784 perIteratorContextNItemsToSkip = nItems] () mutable -> optional<T> {
785 while (i and perIteratorContextNItemsToSkip > 0) {
786 --perIteratorContextNItemsToSkip;
787 ++i;
788 }
789 if (i) {
790 auto result = *i;
791 ++i;
792 return move (result);
793 }
794 return nullopt;
795 };
796 return CreateGenerator (getNext);
797 }
798 template <typename T>
799 Iterable<T> Iterable<T>::Take (size_t nItems) const
800 {
801 // If we have many iterator copies, we need ONE copy of this sharedContext (they all share a reference to the same Iterable)
802 auto sharedContext = Memory::MakeSharedPtr<Iterable<T>> (*this);
803 // If we have many iterator copies, each needs to copy their 'base iterator' (this is their 'index' into the container)
804 // Both the 'sharedContext' and the i' get stored into the lambda closure so they get appropriately copied as you copy iterators
805 // perIteratorContextNItemsToTake also must be cloned per iterator instance
806 function<optional<T> ()> getNext = [sharedContext, i = sharedContext->MakeIterator (),
807 perIteratorContextNItemsToTake = nItems] () mutable -> optional<T> {
808 if (perIteratorContextNItemsToTake == 0) {
809 return nullopt;
810 }
811 perIteratorContextNItemsToTake--;
812 if (i) {
813 auto result = *i;
814 ++i;
815 return move (result);
816 }
817 return nullopt;
818 };
819 return CreateGenerator (getNext);
820 }
821 template <typename T>
822 Iterable<T> Iterable<T>::Slice (size_t from, size_t to) const
823 {
824 // If we have many iterator copies, we need ONE copy of this sharedContext (they all share a reference to the same Iterable)
825 auto sharedContext = Memory::MakeSharedPtr<Iterable<T>> (*this);
826 // If we have many iterator copies, each needs to copy their 'base iterator' (this is their 'index' into the container)
827 // Both the 'sharedContext' and the i' get stored into the lambda closure so they get appropriately copied as you copy iterators
828 // perIteratorContextNItemsToSkip also must be cloned per iterator instance
829 // perIteratorContextNItemsToTake also must be cloned per iterator instance
830 function<optional<T> ()> getNext = [sharedContext, i = sharedContext->MakeIterator (), perIteratorContextNItemsToSkip = from,
831 perIteratorContextNItemsToTake = to - from] () mutable -> optional<T> {
832 while (i and perIteratorContextNItemsToSkip > 0) {
833 --perIteratorContextNItemsToSkip;
834 ++i;
835 }
836 if (perIteratorContextNItemsToTake == 0) {
837 return nullopt;
838 }
839 perIteratorContextNItemsToTake--;
840 if (i) {
841 auto result = *i;
842 ++i;
843 return move (result);
844 }
845 return nullopt;
846 };
847 return CreateGenerator (getNext);
848 }
849 template <typename T>
850 template <Common::IPotentiallyComparer<T> COMPARER>
851 optional<T> Iterable<T>::Top (COMPARER&& cmp) const
852 {
853 /*
854 * 'cmp' defines a sort ORDER, and 'top' is whichever element would come first in that order - ie the
855 * minimum under cmp, which is what min_element computes. With the default greater<T>, that is the
856 * largest element.
857 *
858 * Runs directly off the Iterable's own iterators (Iterator<T> models forward_iterator, which is all
859 * min_element requires), so unlike the Top (n, ...) overloads there is no copy of the container and
860 * no sort: O(S), a single traversal.
861 *
862 * @todo min_element () copy-assigns its running 'smallest' ITERATOR each time it sees a better
863 * element, and copy-assigning an Iterator<T> clones the rep - a heap allocation. So this
864 * allocates once per new-best element: fine on average, but O(S) allocations in the worst
865 * case, which is an already-ordered input (with the default greater<T>, every element is a
866 * new best). Tracking the best VALUE in an optional<T> and looping by hand avoids that
867 * entirely. Purely a performance nit - no API or correctness impact - so not urgent.
868 */
869 auto endI = Iterator<T>{end ()};
870 auto i = min_element (begin (), endI, forward<COMPARER> (cmp));
871 if (i == endI) {
872 return nullopt; // empty Iterable
873 }
874 return *i;
875 }
876 template <typename T>
877 template <Common::IPotentiallyComparer<T> COMPARER>
878 Iterable<T> Iterable<T>::Top (size_t n, COMPARER&& cmp) const
879 {
880 vector<T> tmp = this->As<vector<T>> ();
881 // Clamp rather than special-casing 'n >= size ()': partial_sort with middle == end IS a full sort, so
882 // the n-too-large case needs no separate path (and this avoids a second, virtual, size () call).
883 n = min (n, tmp.size ());
884 partial_sort (tmp.begin (), tmp.begin () + n, tmp.end (), forward<COMPARER> (cmp));
885 size_t idx{0};
886 tmp.erase (tmp.begin () + n, tmp.end ());
887 function<optional<T> ()> getNext = [tmp, idx] () mutable -> optional<T> {
888 if (idx < tmp.size ()) {
889 return tmp[idx++];
890 }
891 else {
892 return nullopt;
893 }
894 };
895 return CreateGenerator (getNext);
896 }
897 template <typename T>
898 inline optional<T> Iterable<T>::Top () const
899 {
900 return Top (std::greater<T>{});
901 }
902 template <typename T>
903 inline Iterable<T> Iterable<T>::Top (size_t n) const
904 {
905 return Top (n, std::greater<T>{});
906 }
907 template <typename T>
908 template <Common::IPotentiallyComparer<T> INORDER_COMPARER_TYPE>
909 inline Iterable<T> Iterable<T>::OrderBy (INORDER_COMPARER_TYPE&& inorderComparer) const
910 {
911 // @todo measure the crossover and auto-choose the policy here - eSeq is a placeholder, not a decision
912 return OrderBy (forward<INORDER_COMPARER_TYPE> (inorderComparer), Execution::SequencePolicy::eSeq);
913 }
914 template <typename T>
915 template <Common::IPotentiallyComparer<T> INORDER_COMPARER_TYPE>
916 Iterable<T> Iterable<T>::OrderBy (INORDER_COMPARER_TYPE&& inorderComparer, [[maybe_unused]] Execution::SequencePolicy seq) const
917 {
918 vector<T> tmp = this->As<vector<T>> ();
919 switch (seq) {
920#if __cpp_lib_execution >= 201603L
921 // only ePar goes parallel; 'default' running sequentially is deliberate - see the dispatch note
922 // on Execution::SequencePolicy
924 stable_sort (execution::par, tmp.begin (), tmp.end (), forward<INORDER_COMPARER_TYPE> (inorderComparer));
925 break;
926 // @todo add other Execution::SequencePolicy cases
927#endif
928 default:
929 stable_sort (tmp.begin (), tmp.end (), forward<INORDER_COMPARER_TYPE> (inorderComparer));
930 break;
931 }
932 /*
933 * Hand back a rep that OWNS the sorted vector and exposes it as contiguous storage, rather than
934 * a generator reading out of a captured copy of it. Two things follow, and the second is the
935 * reason to bother:
936 *
937 * o the sorted data is moved in once and then shared. The generator this replaced captured
938 * the vector BY VALUE, so every copy of the returned Iterable duplicated it.
939 * o the RESULT offers _IRep::PeekContiguousStorage (), so whatever is done to it AFTERWARDS
940 * - As<> (), Contains (), IndexOf (), Min ()/Max ()/Sum (), SequentialEquals () - takes
941 * the bulk fast path. A generator can offer no storage, so sorting used to throw away
942 * the contiguity that everything downstream now depends on.
943 *
944 * Sharing rather than copying is safe because nothing can mutate it: the vector is local, moved
945 * in here, and never handed out except as span<const T>.
946 */
947 struct SortedRep_ : Iterable<T>::_IRep, Memory::UseBlockAllocationIfAppropriate<SortedRep_> {
948 shared_ptr<const vector<T>> fData_; // const pointee: sharing is only safe because nothing can mutate it
949 SortedRep_ (vector<T>&& data)
950 : fData_{Memory::MakeSharedPtr<vector<T>> (move (data))}
951 {
952 }
953 virtual Iterator<T> MakeIterator () const override
954 {
955 // a fresh independent cursor per call, so no iterator tracking is needed
956 return CreateGeneratorIterator<T> ([data = fData_, idx = size_t{0}] () mutable -> optional<T> {
957 return idx < data->size () ? optional<T>{(*data)[idx++]} : nullopt;
958 });
959 }
960 virtual size_t size () const override
961 {
962 return fData_->size ();
963 }
964 virtual bool empty () const override
965 {
966 return fData_->empty ();
967 }
968 virtual optional<span<const T>> PeekContiguousStorage () const override
969 {
970 return span<const T>{*fData_};
971 }
972 virtual shared_ptr<typename Iterable<T>::_IRep> Clone () const override
973 {
974 return Memory::MakeSharedPtr<SortedRep_> (*this); // shares fData_, does not copy it
975 }
976 };
977 return Iterable<T>{Memory::MakeSharedPtr<SortedRep_> (move (tmp))};
978 }
979 template <typename T>
980 template <Common::IPotentiallyComparer<T> INORDER_COMPARER_TYPE>
981 bool Iterable<T>::IsOrderedBy (INORDER_COMPARER_TYPE&& inorderComparer) const
982 {
983 optional<T> last;
984 for (const T& i : *this) {
985 if (last.has_value ()) [[likely]] {
986 // inorderComparer is 'strict inorder' - so case of equals we keep going...
987 if (inorderComparer (i, *last)) [[unlikely]] {
988 return false;
989 }
990 }
991 last = i;
992 }
993 return true;
994 }
995 template <typename T>
996 inline optional<T> Iterable<T>::First () const
997 {
998 auto i = begin ();
999 return i ? *i : optional<T>{};
1000 }
1001 template <typename T>
1002 template <invocable<T> F>
1003 inline optional<T> Iterable<T>::First (F&& that) const
1004 requires (convertible_to<invoke_result_t<F, T>, bool>)
1005 {
1006 constexpr bool kUseIterableRepIteration_ = true; // same semantics, but maybe faster cuz avoids Stroika iterator extra virtual calls overhead
1007 if (kUseIterableRepIteration_) {
1008 Iterator<T> t = this->_fRep->Find (/*findFirst*/ true, forward<F> (that), Execution::SequencePolicy::eSeq);
1009 return t ? optional<T>{*t} : optional<T>{};
1010 }
1011 else {
1012 for (const auto& i : *this) {
1013 if (that (i)) {
1014 return i;
1015 }
1016 }
1017 return nullopt;
1018 }
1019 }
1020 template <typename T>
1021 template <typename RESULT_T>
1022 inline optional<RESULT_T> Iterable<T>::First (const function<optional<RESULT_T> (ArgByValueType<T>)>& that) const
1023 {
1024 RequireNotNull (that);
1025 constexpr bool kUseIterableRepIteration_ = true; // same semantics, but maybe faster cuz avoids Stroika iterator extra virtual calls overhead
1026 if (kUseIterableRepIteration_) {
1027 optional<RESULT_T> result; // actual result captured in side-effect of lambda
1028 auto f = [&that, &result] (ArgByValueType<T> i) { return (result = that (i)).has_value (); };
1029 _SafeReadRepAccessor<_IRep> accessor{this};
1030 Iterator<T> t = accessor._ConstGetRep ().Find (/*findFirst*/ true, f, Execution::SequencePolicy::eSeq);
1031 return t ? result : optional<RESULT_T>{};
1032 }
1033 else {
1034 for (const auto& i : *this) {
1035 if (auto r = that (i)) {
1036 return r;
1037 }
1038 }
1039 return nullopt;
1040 }
1041 }
1042 template <typename T>
1043 inline T Iterable<T>::FirstValue (ArgByValueType<T> defaultValue) const
1044 {
1045 return this->First ().value_or (defaultValue);
1046 }
1047 template <typename T>
1048 template <invocable<T> F>
1049 inline T Iterable<T>::FirstValue (F&& that, ArgByValueType<T> defaultValue) const
1050 requires (convertible_to<invoke_result_t<F, T>, bool>)
1051 {
1052 return this->First (forward<F> (that)).value_or (defaultValue);
1053 }
1054 template <typename T>
1055 optional<T> Iterable<T>::Last () const
1056 {
1057 auto i = begin ();
1058 if (i) {
1059 auto prev = i;
1060 while (i) {
1061 prev = i;
1062 ++i;
1063 }
1064 return *prev;
1065 }
1066 return nullopt;
1067 }
1068 template <typename T>
1069 template <invocable<T> F>
1070 inline optional<T> Iterable<T>::Last (F&& that) const
1071 requires (convertible_to<invoke_result_t<F, T>, bool>)
1072 {
1073 optional<T> result;
1074 for (const auto& i : *this) {
1075 if (that (i)) {
1076 result = i;
1077 }
1078 }
1079 return result;
1080 }
1081 template <typename T>
1082 template <typename RESULT_T>
1083 inline optional<RESULT_T> Iterable<T>::Last (const function<optional<RESULT_T> (ArgByValueType<T>)>& that) const
1084 {
1085 RequireNotNull (that);
1086 optional<T> result;
1087 for (const auto& i : *this) {
1088 if (auto o = that (i)) {
1089 result = *o;
1090 }
1091 }
1092 return result;
1093 }
1094 template <typename T>
1095 inline T Iterable<T>::LastValue (ArgByValueType<T> defaultValue) const
1096 {
1097 return this->Last ().value_or (defaultValue);
1098 }
1099 template <typename T>
1100 template <invocable<T> F>
1101 inline T Iterable<T>::LastValue (F&& that, ArgByValueType<T> defaultValue) const
1102 requires (convertible_to<invoke_result_t<F, T>, bool>)
1103 {
1104 return this->Last (forward<F> (that)).value_or (defaultValue);
1105 }
1106 template <typename T>
1107 bool Iterable<T>::All (const function<bool (ArgByValueType<T>)>& testEachElt) const
1108 {
1109 RequireNotNull (testEachElt);
1110 for (const auto& i : *this) {
1111 if (not testEachElt (i)) {
1112 return false;
1113 }
1114 }
1115 return true;
1116 }
1117 template <typename T>
1118 template <typename REDUCED_TYPE>
1119 optional<REDUCED_TYPE> Iterable<T>::Reduce (const function<REDUCED_TYPE (ArgByValueType<T>, ArgByValueType<T>)>& op) const
1120 {
1121 optional<REDUCED_TYPE> result;
1122 for (const auto& i : *this) {
1123 if (result) {
1124 // NB: ACCUMULATOR FIRST, element second - as std::accumulate and Join () do. Until
1125 // Stroika v3.0d24 this was op (i, *result), which silently REVERSED any non-commutative
1126 // operation (Sum () over String returned "CBA" for {A,B,C}).
1127 result = op (*result, i);
1128 }
1129 else {
1130 result = i;
1131 }
1132 }
1133 return result;
1134 }
1135 template <typename T>
1136 template <typename REDUCED_TYPE>
1137 inline REDUCED_TYPE Iterable<T>::ReduceValue (const function<REDUCED_TYPE (ArgByValueType<T>, ArgByValueType<T>)>& op,
1138 ArgByValueType<REDUCED_TYPE> defaultValue) const
1139 {
1140 return Reduce<REDUCED_TYPE> (op).value_or (defaultValue);
1141 }
1142 template <typename T>
1143 inline optional<T> Iterable<T>::Min () const
1144 {
1145 /*
1146 * FAST PATH - see the note on Max () below; Min ()/Max ()/Sum () all bypass Reduce () rather
1147 * than accelerate it, because Reduce () takes a std::function.
1148 */
1149 {
1150 _SafeReadRepAccessor<> accessor{this};
1151 if (auto s = accessor._ConstGetRep ().PeekContiguousStorage ()) {
1152 if (s->empty ()) [[unlikely]] {
1153 return nullopt;
1154 }
1155 return *min_element (s->begin (), s->end ());
1156 }
1157 }
1158 return Reduce<T> ([] (ArgByValueType<T> lhs, ArgByValueType<T> rhs) -> T { return min (lhs, rhs); });
1159 }
1160 template <typename T>
1161 template <typename RESULT_TYPE>
1162 inline RESULT_TYPE Iterable<T>::MinValue (ArgByValueType<RESULT_TYPE> defaultValue) const
1163 {
1164 return Min ().value_or (defaultValue);
1165 }
1166 template <typename T>
1167 inline optional<T> Iterable<T>::Max () const
1168 {
1169 /*
1170 * FAST PATH - when the backend keeps its elements contiguously.
1171 *
1172 * Note this BYPASSES Reduce () rather than adding a fast path to it. Reduce () takes its
1173 * operation as a std::function<>, so it pays an indirect call PER ELEMENT on top of the
1174 * per-element virtual iteration - and for a cheap T that type-erasure tax is the larger of the
1175 * two (the OrderBy design probe measured it at 3.4x for int; see TODO.md). Going through
1176 * min_element/max_element here keeps the comparison inlined, so both costs go away rather than
1177 * just the iteration. A fast path inside Reduce () would still leave every caller paying the
1178 * std::function call, so it is a separate (smaller) question.
1179 */
1180 {
1181 _SafeReadRepAccessor<> accessor{this};
1182 if (auto s = accessor._ConstGetRep ().PeekContiguousStorage ()) {
1183 if (s->empty ()) [[unlikely]] {
1184 return nullopt;
1185 }
1186 return *max_element (s->begin (), s->end ());
1187 }
1188 }
1189 return Reduce<T> ([] (ArgByValueType<T> lhs, ArgByValueType<T> rhs) -> T { return max (lhs, rhs); });
1190 }
1191 template <typename T>
1192 template <typename RESULT_TYPE>
1193 inline RESULT_TYPE Iterable<T>::MaxValue (ArgByValueType<RESULT_TYPE> defaultValue) const
1194 {
1195 return Max ().value_or (defaultValue);
1197 template <typename T>
1198 template <typename RESULT_TYPE>
1199 inline optional<RESULT_TYPE> Iterable<T>::Mean () const
1200 {
1201 Iterator<T> i = begin ();
1202 if (i == end ()) [[unlikely]] {
1203 return nullopt;
1204 }
1205 return Math::Mean (i, end ());
1206 }
1207 template <typename T>
1208 template <typename RESULT_TYPE>
1209 inline RESULT_TYPE Iterable<T>::MeanValue (ArgByValueType<RESULT_TYPE> defaultValue) const
1210 {
1211 return Mean ().value_or (defaultValue);
1212 }
1213 template <typename T>
1214 template <typename RESULT_TYPE>
1215 inline optional<RESULT_TYPE> Iterable<T>::Sum () const
1216 {
1217 /*
1218 * FAST PATH - see the note on Max (); this bypasses Reduce () for the same reason.
1219 *
1220 * Gated on RESULT_TYPE being T: when they differ, the general path passes the running
1221 * RESULT_TYPE total back through an op declared to take two T, so the accumulation is
1222 * converted to T on every step (ie Sum<double> () over an Iterable<int> truncates each partial
1223 * sum). That quirk is not replicated here; such calls simply keep the general path.
1224 */
1225 if constexpr (same_as<RESULT_TYPE, T>) {
1226 _SafeReadRepAccessor<> accessor{this};
1227 if (auto s = accessor._ConstGetRep ().PeekContiguousStorage ()) {
1228 if (s->empty ()) [[unlikely]] {
1229 return nullopt;
1230 }
1231 T total = *s->begin ();
1232 for (auto i = s->begin () + 1; i != s->end (); ++i) {
1233 total = total + *i; // accumulator first, matching Reduce ()
1234 }
1235 return total;
1236 }
1237 }
1238 return Reduce<RESULT_TYPE> ([] (ArgByValueType<T> lhs, ArgByValueType<T> rhs) { return lhs + rhs; });
1239 }
1240 template <typename T>
1241 template <typename RESULT_TYPE>
1242 inline RESULT_TYPE Iterable<T>::SumValue (ArgByValueType<RESULT_TYPE> defaultValue) const
1243 {
1244 return Sum ().value_or (defaultValue);
1245 }
1246 template <typename T>
1247 template <constructible_from<T> RESULT_TYPE, Common::IPotentiallyComparer<RESULT_TYPE> INORDER_COMPARE_FUNCTION>
1248 inline optional<RESULT_TYPE> Iterable<T>::Median (const INORDER_COMPARE_FUNCTION& compare) const
1249 {
1250 Iterator<T> i = begin ();
1251 if (i == end ()) [[unlikely]] {
1252 return nullopt;
1253 }
1254 return Math::Median<RESULT_TYPE> (i, end (), compare);
1255 }
1256 template <typename T>
1257 template <constructible_from<T> RESULT_TYPE>
1258 inline RESULT_TYPE Iterable<T>::MedianValue (ArgByValueType<RESULT_TYPE> defaultValue) const
1259 {
1260 return Median ().value_or (defaultValue);
1261 }
1262 template <typename T>
1264 {
1265 switch (count) {
1266 case 0:
1267 return Iterable<T>{};
1268 case 1:
1269 return *this;
1270 default: {
1271 // Somewhat simplistic / inefficient implementation
1272 vector<T> origList = this->As<vector<T>> ();
1273 size_t repeatCountIndex{1}; // start at one, cuz we don't copy the zeroth time
1274 size_t innerIndex{0};
1275 function<optional<T> ()> getNext = [origList, repeatCountIndex, innerIndex, count] () mutable -> optional<T> {
1276 Again:
1277 if (innerIndex < origList.size ()) [[likely]] {
1278 return origList[innerIndex++];
1279 }
1280 if (repeatCountIndex < count) [[likely]] {
1281 ++repeatCountIndex;
1282 innerIndex = 0;
1283 goto Again;
1284 }
1285 return nullopt;
1286 };
1287 return CreateGenerator (getNext);
1288 }
1289 }
1290 }
1291 template <typename T>
1292 inline bool Iterable<T>::Any () const
1293 {
1294 return not empty ();
1295 }
1296 template <typename T>
1297 inline bool Iterable<T>::Any (const function<bool (ArgByValueType<T>)>& includeIfTrue) const
1298 {
1299 return static_cast<bool> (Find (includeIfTrue));
1300 }
1301 template <typename T>
1302 inline size_t Iterable<T>::Count () const
1303 {
1304 return size ();
1305 }
1306 template <typename T>
1307 inline size_t Iterable<T>::Count (const function<bool (ArgByValueType<T>)>& includeIfTrue) const
1309 size_t cnt{};
1310 Apply ([&] (ArgByValueType<T> a) {
1311 if (includeIfTrue (a))
1312 ++cnt;
1313 });
1314 Ensure (cnt == Where (includeIfTrue).size ());
1315 return cnt;
1316 }
1317 template <typename T>
1318 inline size_t Iterable<T>::length () const
1319 {
1320 return size ();
1321 }
1322 template <typename T>
1324 {
1325 return MakeIterator ();
1326 }
1327 template <typename T>
1328 constexpr default_sentinel_t Iterable<T>::end () noexcept
1331 }
1332 template <typename T>
1333 inline void Iterable<T>::Apply (const function<void (ArgByValueType<T> item)>& doToElement) const
1334 {
1335 // @todo measure the crossover and auto-choose the policy here - eSeq is a placeholder, not a decision
1336 Apply (doToElement, Execution::SequencePolicy::eSeq);
1337 }
1338 template <typename T>
1339 inline void Iterable<T>::Apply (const function<void (ArgByValueType<T> item)>& doToElement, Execution::SequencePolicy seq) const
1340 {
1341 RequireNotNull (doToElement);
1342 _SafeReadRepAccessor<> accessor{this};
1343 accessor._ConstGetRep ().Apply (doToElement, seq);
1344 }
1345 template <typename T>
1346 template <predicate<T> THAT_FUNCTION>
1347 inline Iterator<T> Iterable<T>::Find (THAT_FUNCTION&& that) const
1348 {
1349 // @todo measure the crossover and auto-choose the policy here - eSeq is a placeholder, not a decision
1350 return Find (forward<THAT_FUNCTION> (that), Execution::SequencePolicy::eSeq);
1351 }
1352 template <typename T>
1353 template <predicate<T> THAT_FUNCTION>
1354 inline Iterator<T> Iterable<T>::Find (THAT_FUNCTION&& that, Execution::SequencePolicy seq) const
1355 {
1356 // NB: This transforms perfectly forwarded 'THAT_FUNCTION' and converts it to std::function<> - preventing further inlining at this point -
1357 // just so it can be done
1358 _SafeReadRepAccessor<> accessor{this};
1359 return accessor._ConstGetRep ().Find (/*findFirst*/ false, forward<THAT_FUNCTION> (that), seq);
1360 }
1361 template <typename T>
1362 template <Common::IPotentiallyComparer<T> EQUALS_COMPARER>
1363 inline Iterator<T> Iterable<T>::Find (ArgByValueType<T> v, EQUALS_COMPARER&& equalsComparer) const
1364 {
1365 // @todo measure the crossover and auto-choose the policy here - eSeq is a placeholder, not a decision
1366 return Find (v, forward<EQUALS_COMPARER> (equalsComparer), Execution::SequencePolicy::eSeq);
1367 }
1368 template <typename T>
1369 template <Common::IPotentiallyComparer<T> EQUALS_COMPARER>
1370 inline Iterator<T> Iterable<T>::Find (ArgByValueType<T> v, EQUALS_COMPARER&& equalsComparer, Execution::SequencePolicy seq) const
1371 {
1372 /*
1373 * NB: deliberately NO _IRep::PeekContiguousStorage () fast path here, unlike Contains () and
1374 * Sequence<T>::IndexOf (). Find () must return a live Iterator<T> positioned at the match, and a
1375 * span can only give a POSITION - there is no way to synthesize a Stroika iterator from a
1376 * pointer, and _IRep offers only MakeIterator (), never an 'iterator at index N'. So a span
1377 * would locate the element quickly and then have to walk to it anyway, for no net gain.
1378 *
1379 * The hook that DOES fit a contiguous backend here is _IRep::Find_equal_to () just below, which
1380 * a backend can override to use its own storage AND its own iterator construction - which is how
1381 * the tree/hash backends already accelerate this. That is per-backend work, not a generic span
1382 * path.
1383 */
1384 if constexpr (same_as<remove_cvref_t<EQUALS_COMPARER>, equal_to<T>> and Common::IEqualToOptimizable<T>) {
1385 // This CAN be much faster than the default implementation for this special (but common) case (often a tree structure will have been maintained making this find faster)
1386 _SafeReadRepAccessor<> accessor{this};
1387 return accessor._ConstGetRep ().Find_equal_to (v, seq);
1388 }
1389 else {
1390 return Find ([v, equalsComparer] (ArgByValueType<T> arg) { return equalsComparer (v, arg); }, seq);
1391 }
1392 }
1393 template <typename T>
1394 template <predicate<T> THAT_FUNCTION>
1395 inline Iterator<T> Iterable<T>::Find (const Iterator<T>& startAt, THAT_FUNCTION&& that) const
1396 {
1397 // @todo measure the crossover and auto-choose the policy here - eSeq is a placeholder, not a decision
1398 return Find (startAt, forward<THAT_FUNCTION> (that), Execution::SequencePolicy::eSeq);
1399 }
1400 template <typename T>
1401 template <predicate<T> THAT_FUNCTION>
1402 inline Iterator<T> Iterable<T>::Find (const Iterator<T>& startAt, THAT_FUNCTION&& that, [[maybe_unused]] Execution::SequencePolicy seq) const
1403 {
1404 for (Iterator<T> i = startAt; i != end (); ++i) {
1405 if (forward<THAT_FUNCTION> (that) (*i)) [[unlikely]] {
1406 return i;
1407 }
1408 }
1409 return end ();
1411 template <typename T>
1412 template <Common::IPotentiallyComparer<T> EQUALS_COMPARER>
1413 inline Iterator<T> Iterable<T>::Find (const Iterator<T>& startAt, ArgByValueType<T> v, EQUALS_COMPARER&& equalsComparer) const
1414 {
1415 // @todo measure the crossover and auto-choose the policy here - eSeq is a placeholder, not a decision
1416 return Find (startAt, v, forward<EQUALS_COMPARER> (equalsComparer), Execution::SequencePolicy::eSeq);
1417 }
1418 template <typename T>
1419 template <Common::IPotentiallyComparer<T> EQUALS_COMPARER>
1420 Iterator<T> Iterable<T>::Find (const Iterator<T>& startAt, ArgByValueType<T> v, EQUALS_COMPARER&& equalsComparer,
1421 [[maybe_unused]] Execution::SequencePolicy seq) const
1422 {
1423 for (Iterator<T> i = startAt; i != end (); ++i) {
1424 if (forward<EQUALS_COMPARER> (equalsComparer) (v, *i)) [[unlikely]] {
1425 return i;
1426 }
1427 }
1428 return end ();
1429 }
1430 template <typename T>
1431 template <IIterableOfFrom<T> CONTAINER_OF_T, typename... CONTAINER_OF_T_CONSTRUCTOR_ARGS>
1432 inline CONTAINER_OF_T Iterable<T>::As (CONTAINER_OF_T_CONSTRUCTOR_ARGS... args) const
1433 {
1434 /*
1435 * FAST PATH - when this backend keeps its elements contiguously, construct the target directly
1436 * from that buffer. A span's iterators are (effectively) pointers, so the target's range CTOR
1437 * can bulk-copy - for trivial T, a memcpy - rather than walking this Iterable one element at a
1438 * time through virtual calls.
1439 *
1440 * Applies to EVERY target type. Nothing about it is vector-specific, and the same saving is
1441 * available to As<list<T>> (), As<Sequence<T>> (), ... - though where the target's own insertion
1442 * cost dominates (a node per element) the saving is a much smaller share of the total.
1443 *
1444 * Guarded by constructible_from so this stays purely additive: a CONTAINER_OF_T that accepts
1445 * Stroika's iterators but not a plain pointer pair simply keeps the slow path below.
1446 *
1447 * The accessor must outlive the span, which is why it is a named local rather than a temporary
1448 * - see the precondition on _IRep::PeekContiguousStorage ().
1449 *
1450 * Backends with no contiguous storage (linked lists, hash tables, skip lists, and notably the
1451 * generator rep that Iterable<T> wraps a plain STL container in) return nullopt and fall through
1452 * to a generic implementation.
1453 */
1454 using ContiguousIterator_ = typename span<const T>::iterator; // NB: not necessarily const T* (eg checked iterators)
1455 if constexpr (constructible_from<CONTAINER_OF_T, CONTAINER_OF_T_CONSTRUCTOR_ARGS..., ContiguousIterator_, ContiguousIterator_>) {
1456 _SafeReadRepAccessor<> accessor{this};
1457 if (auto s = accessor._ConstGetRep ().PeekContiguousStorage ()) {
1458 return CONTAINER_OF_T (forward<CONTAINER_OF_T_CONSTRUCTOR_ARGS> (args)..., s->begin (), s->end ());
1459 }
1460 }
1461 // some containers require two iterators as arguments, but Stroika ones work with default_sentinel_t or iterator
1462 // use CONTAINER_OF_T () instead of CONTAINER_OF_T{} because we do want to allow coercion here - since use explicitly called As<>
1463 if constexpr (derived_from<CONTAINER_OF_T, Iterable<T>>) {
1464 return CONTAINER_OF_T (forward<CONTAINER_OF_T_CONSTRUCTOR_ARGS> (args)..., begin (), end ());
1465 }
1466 else {
1467 return CONTAINER_OF_T (forward<CONTAINER_OF_T_CONSTRUCTOR_ARGS> (args)..., begin (), Iterator<T>{end ()});
1468 }
1469 }
1470 template <typename T>
1471 inline T Iterable<T>::Nth (ptrdiff_t n) const
1472 {
1473 Require (n < static_cast<ptrdiff_t> (size ()));
1474 Require (n > -static_cast<ptrdiff_t> (size ()));
1475 size_t useIndex = n >= 0 ? static_cast<size_t> (n) : static_cast<size_t> (n + static_cast<ptrdiff_t> (size ()));
1476 size_t idx = useIndex; // countdown
1477 for (const T& i : *this) {
1478 if (idx == 0) {
1479 return i;
1480 }
1481 --idx;
1482 }
1484 return *begin ();
1485 }
1486 template <typename T>
1487 inline T Iterable<T>::NthValue (ptrdiff_t n, ArgByValueType<T> defaultValue) const
1488 {
1489 size_t useIndex = n >= 0 ? static_cast<size_t> (n) : static_cast<size_t> (n + static_cast<ptrdiff_t> (size ()));
1490 size_t idx = useIndex; // countdown
1491 for (const T& i : *this) {
1492 if (idx == 0) {
1493 return i;
1494 }
1495 --idx;
1496 }
1497 return defaultValue;
1498 }
1499
1500 /*
1501 ********************************************************************************
1502 ******************** Iterable<T>::SequentialEqualsComparer *********************
1503 ********************************************************************************
1504 */
1505 template <typename T>
1506 template <qCompilerAndStdLib_ConstraintDiffersInTemplateRedeclaration_BWA (IEqualsComparer<T>) T_EQUALS_COMPARER>
1507 constexpr Iterable<T>::SequentialEqualsComparer<T_EQUALS_COMPARER>::SequentialEqualsComparer (const T_EQUALS_COMPARER& elementEqualsComparer)
1508 : fElementComparer{elementEqualsComparer}
1509 {
1510 }
1511 DISABLE_COMPILER_MSC_WARNING_START (4996)
1512 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wdeprecated-declarations\"")
1513 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
1514 template <typename T>
1515 template <qCompilerAndStdLib_ConstraintDiffersInTemplateRedeclaration_BWA (IEqualsComparer<T>) T_EQUALS_COMPARER>
1516 constexpr Iterable<T>::SequentialEqualsComparer<T_EQUALS_COMPARER>::SequentialEqualsComparer (const T_EQUALS_COMPARER& elementEqualsComparer,
1517 [[maybe_unused]] bool useIterableSize)
1518 : fElementComparer{elementEqualsComparer}
1519 {
1520 }
1521 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
1522 DISABLE_COMPILER_CLANG_WARNING_END ("clang diagnostic ignored \"-Wdeprecated-declarations\"")
1523 DISABLE_COMPILER_MSC_WARNING_END (4996)
1524 template <typename T>
1525 template <qCompilerAndStdLib_ConstraintDiffersInTemplateRedeclaration_BWA (IEqualsComparer<T>) T_EQUALS_COMPARER>
1526 inline bool Iterable<T>::SequentialEqualsComparer<T_EQUALS_COMPARER>::operator() (const Iterable& lhs, const Iterable& rhs) const
1527 {
1528 return SequentialEquals (lhs, rhs, fElementComparer);
1529 }
1530
1531 /*
1532 ********************************************************************************
1533 ******************** Iterable<T>::SequentialThreeWayComparer *******************
1534 ********************************************************************************
1535 */
1536 template <typename T>
1537 template <qCompilerAndStdLib_ConstraintDiffersInTemplateRedeclaration_BWA (IThreeWayComparer<T>) T_THREEWAY_COMPARER>
1538 constexpr Iterable<T>::SequentialThreeWayComparer<T_THREEWAY_COMPARER>::SequentialThreeWayComparer (const T_THREEWAY_COMPARER& elementComparer)
1539 : fElementComparer{elementComparer}
1540 {
1541 }
1542 DISABLE_COMPILER_MSC_WARNING_START (4701)
1543 template <typename T>
1544 template <qCompilerAndStdLib_ConstraintDiffersInTemplateRedeclaration_BWA (IThreeWayComparer<T>) T_THREEWAY_COMPARER>
1545 inline auto Iterable<T>::SequentialThreeWayComparer<T_THREEWAY_COMPARER>::operator() (const Iterable& lhs, const Iterable& rhs) const
1546 {
1547 auto li = lhs.begin ();
1548 auto le = lhs.end ();
1549 auto ri = rhs.begin ();
1550 auto re = rhs.end ();
1551 DISABLE_COMPILER_MSC_WARNING_START (6001)
1552 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
1553 // no need for c' initialization cuz only used in else return at end, but never get there
1554 // unless set at least once
1555 optional<strong_ordering> c;
1556 while ((li != le) and (ri != re) and (c = fElementComparer (*li, *ri)) == strong_ordering::equal) {
1557 ++li;
1558 ++ri;
1559 }
1560 if (li == le) {
1561 if (ri == re) {
1562 return strong_ordering::equal; // all items same and loop ended with both things at end
1563 }
1564 else {
1565 return strong_ordering::less; // lhs shorter but an initial sequence of rhs
1566 }
1567 }
1568 else if (ri == re) {
1569 return strong_ordering::greater; // rhs shorter but an initial sequence of lhs
1570 }
1571 else {
1572 Assert (li != le and ri != re);
1573 Assert (c == fElementComparer (*li, *ri));
1574 return c.value ();
1575 }
1576 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
1577 DISABLE_COMPILER_MSC_WARNING_END (6001)
1578 }
1579 DISABLE_COMPILER_MSC_WARNING_END (4701)
#define EnsureNotNull(p)
Definition Assertions.h:341
#define RequireNotReached()
Definition Assertions.h:386
#define qStroika_Foundation_Debug_AssertionsChecked
The qStroika_Foundation_Debug_AssertionsChecked flag determines if assertions are checked and validat...
Definition Assertions.h:49
#define RequireNotNull(p)
Definition Assertions.h:348
#define RequireExpression(c)
Definition Assertions.h:268
#define AssertNotReached()
Definition Assertions.h:356
#define EnsureMember(p, c)
Definition Assertions.h:320
auto MakeSharedPtr(ARGS_TYPE &&... args) -> shared_ptr< T >
same as make_shared, but if type T has block allocation, then use block allocation for the 'shared pa...
T UncheckedDynamicCast(T1 &&arg) noexcept
return the same value as dynamic_cast<T> would have, except instead of checking nullptr,...
Definition Cast.inl:13
Iterable< T > CreateGenerator(const function< optional< T >()> &getNext)
Create an Iterable<T> from a function that returns optional<T> - treating nullopt as meaning the END ...
Definition Generator.inl:58
RESULT_TYPE Median(const ITERATOR_OF_T &start, ITERATOR_OF_T2 &&end, INORDER_COMPARE_FUNCTION &&compare={})
Median of a collection of numbers computed.
nonvirtual SharingState GetSharingState() const
virtual Iterator< value_type > MakeIterator() const =0
virtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement, Execution::SequencePolicy seq) const
Definition Iterable.inl:63
virtual optional< span< const value_type > > PeekContiguousStorage() const
Hand back this backend's elements as one contiguous, in-iteration-order block - or nullopt if it has ...
Definition Iterable.inl:112
virtual Iterator< value_type > Find_equal_to(const ArgByValueType< T > &v, Execution::SequencePolicy seq) const
Definition Iterable.inl:83
Iterable<T> is a base class for containers which easily produce an Iterator<T> to traverse them.
Definition Iterable.h:238
nonvirtual RESULT_T Join(const CONVERT_TO_RESULT &convertToResult=kDefaultToStringConverter<>, const COMBINER &combiner=Characters::kDefaultStringCombiner) const
ape the JavaScript/python 'join' function - take the parts of 'this' iterable and combine them into a...
nonvirtual RESULT_TYPE MaxValue(ArgByValueType< RESULT_TYPE > defaultValue={}) const
nonvirtual Iterable< T > Slice(size_t from, size_t to) const
Definition Iterable.inl:822
static bool SetEquals(const LHS_CONTAINER_TYPE &lhs, const RHS_CONTAINER_TYPE &rhs, EQUALS_COMPARER &&equalsComparer=EQUALS_COMPARER{})
Definition Iterable.inl:360
nonvirtual bool Any() const
Any() same as not empty (); Any (includeIfTrue) returns true iff includeIfTrue returns true on any va...
nonvirtual optional< T > Max() const
nonvirtual RESULT_TYPE MedianValue(ArgByValueType< RESULT_TYPE > defaultValue={}) const
nonvirtual optional< RESULT_TYPE > Mean() const
nonvirtual Iterator< T > Find(THAT_FUNCTION &&that) const
Run the argument bool-returning function (or lambda) on the elements of the container,...
nonvirtual size_t length() const
STL-ish alias for size() - really in STL only used in string, I think, but still makes sense as an al...
nonvirtual CONTAINER_OF_T As(CONTAINER_OF_T_CONSTRUCTOR_ARGS... args) const
nonvirtual Iterable< T > Distinct(EQUALS_COMPARER &&equalsComparer=EQUALS_COMPARER{}) const
nonvirtual RESULT_CONTAINER Map(ELEMENT_MAPPER &&elementMapper) const
functional API which iterates over all members of an Iterable, applies a map function to each element...
nonvirtual size_t Count() const
with no args, same as size, with function filter arg, returns number of items that pass.
nonvirtual bool IsOrderedBy(INORDER_COMPARER_TYPE &&inorderComparer=INORDER_COMPARER_TYPE{}) const
nonvirtual Iterable< T > Repeat(size_t count) const
nonvirtual optional< T > First() const
return first element in iterable, or if 'that' specified, first where 'that' is true,...
Definition Iterable.inl:996
T value_type
value_type is an alias for the type iterated over - like vector<T>::value_type
Definition Iterable.h:251
nonvirtual RESULT_TYPE MinValue(ArgByValueType< RESULT_TYPE > defaultValue={}) const
nonvirtual bool All(const function< bool(ArgByValueType< T >)> &testEachElt) const
return true iff argument predicate returns true for each element of the iterable
nonvirtual bool Contains(ArgByValueType< T > element, EQUALS_COMPARER &&equalsComparer=EQUALS_COMPARER{}) const
nonvirtual optional< T > Min() const
nonvirtual T NthValue(ptrdiff_t n, ArgByValueType< T > defaultValue={}) const
Find the Nth element of the Iterable<>, but allow for n to be out of range, and just return argument ...
nonvirtual size_t size() const
Returns the number of items contained.
Definition Iterable.inl:311
nonvirtual RESULT_CONTAINER Where(INCLUDE_PREDICATE &&includeIfTrue) const
produce a subset of this iterable where argument function returns true
nonvirtual void Apply(const function< void(ArgByValueType< T > item)> &doToElement) const
Run the argument function (or lambda) on each element of the container.
nonvirtual T Nth(ptrdiff_t n) const
Find the Nth element of the Iterable<>
nonvirtual Iterable< T > Take(size_t nItems) const
Definition Iterable.inl:799
nonvirtual RESULT_TYPE MeanValue(ArgByValueType< RESULT_TYPE > defaultValue={}) const
nonvirtual optional< RESULT_TYPE > Median(const INORDER_COMPARE_FUNCTION &compare={}) const
nonvirtual Iterable< T > Skip(size_t nItems) const
Definition Iterable.inl:776
nonvirtual RESULT_TYPE SumValue(ArgByValueType< RESULT_TYPE > defaultValue={}) const
nonvirtual optional< REDUCED_TYPE > Reduce(const function< REDUCED_TYPE(ArgByValueType< T >, ArgByValueType< T >)> &op) const
Walk the entire list of items, and use the argument 'op' to combine (reduce) items to a resulting sin...
nonvirtual Iterator< T > begin() const
Support for ranged for, and STL syntax in general.
nonvirtual optional< T > Top() const
return the top/largest value (or the top N values) from this Iterable<T>
Definition Iterable.inl:898
Iterable(const Iterable &) noexcept=default
Iterable are safely copyable (by value). Since Iterable uses COW, this just copies the underlying poi...
nonvirtual optional< RESULT_TYPE > Sum() const
nonvirtual Memory::SharedByValueSupport::SharingState _GetSharingState() const
Definition Iterable.inl:300
static bool SequentialEquals(const LHS_CONTAINER_TYPE &lhs, const RHS_CONTAINER_TYPE &rhs, EQUALS_COMPARER &&equalsComparer=EQUALS_COMPARER{})
Definition Iterable.inl:436
nonvirtual bool empty() const
Returns true iff size() == 0.
Definition Iterable.inl:317
nonvirtual T LastValue(ArgByValueType< T > defaultValue={}) const
static bool MultiSetEquals(const LHS_CONTAINER_TYPE &lhs, const RHS_CONTAINER_TYPE &rhs, EQUALS_COMPARER &&equalsComparer=EQUALS_COMPARER{})
Definition Iterable.inl:401
nonvirtual optional< T > Last() const
return last element in iterable, or if 'that' specified, last where 'that' is true,...
nonvirtual T FirstValue(ArgByValueType< T > defaultValue={}) const
return first element in iterable provided default
static constexpr default_sentinel_t end() noexcept
Support for ranged for, and STL syntax in general.
nonvirtual REDUCED_TYPE ReduceValue(const function< REDUCED_TYPE(ArgByValueType< T >, ArgByValueType< T >)> &op, ArgByValueType< REDUCED_TYPE > defaultValue={}) const
nonvirtual Iterable< T > OrderBy(INORDER_COMPARER_TYPE &&inorderComparer=INORDER_COMPARER_TYPE{}) const
nonvirtual Iterator< T > MakeIterator() const
Create an iterator object which can be used to traverse the 'Iterable'.
Definition Iterable.inl:305
An Iterator<T> is a copyable object which allows traversing the contents of some container.
Definition Iterator.h:253
static constexpr default_sentinel_t GetEmptyIterator() noexcept
Used by someContainer::end ()
Definition Iterator.inl:243
SequencePolicy
equivalent which of 4 types being used std::execution::sequenced_policy, parallel_policy,...
@ ePar
must synchronize shared data, can use mutex (or atomics), cuz each parallel execution in real thread
@ eSeq
default case - not parallelized
utility for generic code that wishes to add something to a somewhat arbitrary container,...
Definition Adder.h:57