Stroika Library 3.0d24
 
Loading...
Searching...
No Matches
Array.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2026. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Containers_DataStructures_Array_h_
5#define _Stroika_Foundation_Containers_DataStructures_Array_h_
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <concepts>
10#include <optional>
11
12#include "Stroika/Foundation/Common/Common.h"
14#include "Stroika/Foundation/Containers/Common.h"
16#include "Stroika/Foundation/Execution/Common.h"
17#include "Stroika/Foundation/Memory/Common.h"
18
19/**
20 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
21 */
22
24
26
27 /**
28 * \brief very similar to std::vector<T>
29 *
30 * This class provides an array abstraction, where the size can be set dynamically, and
31 * extra sluff is maintained off the end to reduce copying from reallocs.
32 * Only items 0..size ()-1 are kept constructed. The rest (size()+1
33 * ..fSlotsAlloced) are uninitialized memory. This is important because
34 * it means you can count on DTORs of your T being called when you
35 * remove them from contains, not when the caches happen to empty.
36 *
37 * Array<T> is simple data structure implementation. It is not intended to be directly
38 * used by programmers, except in implementing concrete container reps (and occasionally in
39 * performance sensitive situations, though std::vector<> maybe a better choice then).
40 *
41 * Array<T> is a template which provides a dynamic array class (very similar to std::vector). Elements
42 * of type T can be assigned, and accessed much like a normal array, except
43 * that when debug is on, accesses are range-checked.
44 *
45 * Array<T> also provides a dynamic sizing capability. It reallocs its
46 * underlying storage is such a ways as to keep a buffer of roughly 20%
47 * extra (see Support::ReserveTweaks::GetScaledUpCapacity), so that reallocs on resizes
48 * only occur log(n) times on n appends.
49 * To save even this space, you can call shrink_to_fit().
50 */
51 template <typename T>
53 public:
54 using value_type = T;
55
56 public:
57 /**
58 * Basic (mostly internal) element used by ForwardIterator. Abstract name so can be referenced generically across 'DataStructure' objects
59 */
60 using UnderlyingIteratorRep = size_t;
61
62 public:
63 /**
64 */
65 Array () = default;
66 Array (Array&& from) noexcept;
67 Array (const Array& from);
68
69 public:
70 ~Array ();
71
72 public:
73 nonvirtual Array& operator= (const Array& rhs);
74
75 public:
76 class ForwardIterator;
77 class BackwardIterator;
78
79 public:
80 /**
81 * \brief returns internal pointer to data - which is unsynchronized, and only guaranteed valid until the next non-const array method.
82 */
83 nonvirtual T* data () noexcept;
84 nonvirtual const T* data () const noexcept;
85
86 public:
87 /**
88 * \note Runtime performance/complexity:
89 * Always: constant
90 */
91 nonvirtual T GetAt (size_t i) const;
92
93 public:
94 /**
95 * Not a great API, since cannot check it very well. However, its more efficient when storing a larger object and you need
96 * to update just part of it.
97 *
98 * \note Runtime performance/complexity:
99 * Always: constant
100 */
101 nonvirtual T* PeekAt (size_t i);
102 nonvirtual const T* PeekAt (size_t i) const;
103
104 public:
105 /**
106 * \note Runtime performance/complexity:
107 * Always: constant
108 */
109 nonvirtual void SetAt (size_t i, ArgByValueType<T> item);
110
111 public:
112 /**
113 * \note Runtime performance/complexity:
114 * Always: constant
115 */
116 nonvirtual T& operator[] (size_t i);
117 nonvirtual T operator[] (size_t i) const;
118
119 public:
120 /**
121 * \note Runtime performance/complexity:
122 * Always: constant
123 */
124 nonvirtual size_t size () const;
125
126 public:
127 /**
128 * \note Runtime performance/complexity:
129 * Always: constant
130 */
131 nonvirtual bool empty () const;
132
133 public:
134 /**
135 * \note Runtime performance/complexity:
136 * Worst Case: O(N)
137 * Typical Case: ?? for small changes often constant, but if enuf change of size O(N) growing. Less shrinking.
138 */
139 nonvirtual void SetLength (size_t newLength, ArgByValueType<T> fillValue);
140
141 public:
142 /**
143 * \note index may == size() - in which case, we are appending.
144 *
145 * \note Runtime performance/complexity:
146 * Worst Case: O(N)
147 * Typical: depends on i, and Capacity - if need to change capacity O(N), and if near start of array O(N), and if near end of the array (append) can be cheap
148 */
149 nonvirtual void Insert (size_t index, ArgByValueType<T> item);
150 template <Memory::ISpanOfT<T> SPAN_T>
151 nonvirtual void Insert (size_t at, const SPAN_T& copyFrom);
152 nonvirtual void Insert (const ForwardIterator& i, ArgByValueType<T> item);
153 nonvirtual void Insert (const BackwardIterator& i, ArgByValueType<T> item);
154
155 public:
156#if qCompilerAndStdLib_MemoryInsertAt_Buggy
157 nonvirtual void Insert_BWA (size_t index, ArgByValueType<T> item);
158#endif
159
160 public:
161 /**
162 * \brief STL-ish alias for Insert (size(), item)
163 *
164 * @aliases Append
165 *
166 * \note Runtime performance/complexity:
167 * Worst Case: O(N)
168 * Typical: constant
169 */
170 nonvirtual void push_back (ArgByValueType<T> item);
171
172 public:
173 /**
174 * \note Runtime performance/complexity:
175 * Worst Case: O(N) - if !trivial_type
176 * Typical: constant
177 */
178 nonvirtual void clear ();
179
180 public:
181 /**
182 * \note Runtime performance/complexity:
183 * Always: O(N)
184 *
185 * \note The overload taking NO SequencePolicy leaves the choice to the implementation. Today it
186 * runs sequentially, but that is NOT a promise: it may become eSeq, ePar, eParUnseq or
187 * eUnseq (SIMD). So 'doToElement' must be safe under ANY of them:
188 *
189 * o No unsynchronized side effects on shared state - it may run on several threads at once.
190 * o No dependence on the ORDER elements are visited in, nor on which thread visits them.
191 * o It must NOT throw - a policy-taking std::for_each () calls std::terminate () when an
192 * element access function exits via an exception rather than propagating it, and this
193 * one really does reach std::for_each (execution::par, ...) - see the .inl.
194 * o Under eUnseq / eParUnseq, additionally no allocation and no locking - calls may
195 * interleave within a single thread, so a mutex can deadlock against itself.
196 *
197 * Pass Execution::SequencePolicy::eSeq explicitly if 'doToElement' cannot meet all of that.
198 */
199 template <invocable<T> FUNCTION>
200 nonvirtual void Apply (FUNCTION&& doToElement) const;
201 template <invocable<T> FUNCTION>
202 nonvirtual void Apply (FUNCTION&& doToElement, Execution::SequencePolicy seq) const;
203
204 public:
205 class IteratorBase;
206
207 public:
208 /**
209 */
210 nonvirtual ForwardIterator begin () const;
211
212 public:
213 /**
214 */
215 constexpr ForwardIterator end () const;
216
217 public:
218 /**
219 * Return ForwardIterator of first place in the array matching, or nullptr if not found
220 *
221 * \note Runtime performance/complexity:
222 * Worst Case: O(N)
223 * Typical: O(N), but can be less if systematically finding entries near start of array
224 *
225 * \note in Stroika v2.1, this returned value == size() means not found, but now uses optional to make clearer
226 * and more similar to LinkedList find ...
227 *
228 * \note before Stroika v3.0d10, this returned optional<size_t>
229 *
230 * EQUALS_COMPARER OVERLOAD : Returns pointer to T (or nullptr if not found). Lifetime of T* only til next call on this.
231 *
232 * @aliases Lookup, First, Contains (sort of)
233 */
234 template <predicate<T> FUNCTION>
235 nonvirtual ForwardIterator Find (FUNCTION&& firstThat) const;
236 template <typename EQUALS_COMPARER = equal_to<T>>
237 nonvirtual const T* Find (ArgByValueType<T> item, EQUALS_COMPARER&& equalsComparer = {}) const;
238 template <typename EQUALS_COMPARER = equal_to<T>>
239 nonvirtual T* Find (ArgByValueType<T> item, EQUALS_COMPARER&& equalsComparer = {});
240
241 public:
242 /*
243 * Memory savings/optimization methods. Use this to tune usage
244 * of arrays so that they don't waste time in Realloc's.
245 */
246 nonvirtual size_t capacity () const;
247
248 public:
249 /**
250 * \brief sets the reserved capacity to slotsAlloced
251 *
252 * \pre size () <= slotsAlloced
253 *
254 * \see also more likely to use ReserveAtLeast
255 */
256 nonvirtual void reserve (size_t slotsAlloced);
257
258 public:
259 /**
260 * \note slotsAllocated maybe < size() - but then it would be ignored, since this only grows the capacity
261 */
262 nonvirtual void ReserveAtLeast (size_t slotsAlloced);
263
264 public:
265 /**
266 */
267 nonvirtual void shrink_to_fit ();
268
269 public:
270 /*
271 * Support for COW (CopyOnWrite):
272 *
273 * Take iterator 'pi' which is originally a valid iterator from 'movedFrom' - and replace *pi with a valid
274 * iterator from 'this' - which points at the same logical position. This requires that this container
275 * was just 'copied' from 'movedFrom' - and is used to produce an equivalent iterator (since iterators are tied to
276 * the container they were iterating over).
277 */
278 nonvirtual void MoveIteratorHereAfterClone (IteratorBase* pi, const Array* movedFrom) const;
279
280 public:
281 /**
282 * \note Runtime performance/complexity:
283 * Worst Case: O(N)
284 * Typical: depends on index but typically O(N) (can be less if removing from end of Array)
285 *
286 * \see erase () - same as Remove(it) but returns iterator of 'next'
287 */
288 nonvirtual void Remove (const ForwardIterator& i);
289 nonvirtual void Remove (const BackwardIterator& i);
290 nonvirtual void Remove (size_t index) noexcept;
291 nonvirtual void Remove (size_t from, size_t to) noexcept;
292
293 public:
294 /**
295 * \brief remove the element at i, and return valid iterator to the element that was following it (which can be empty iterator)
296 *
297 * \pre i != end ()
298 *
299 * \brief see https://en.cppreference.com/w/cpp/container/vector/erase
300 */
301 nonvirtual ForwardIterator erase (const ForwardIterator& i);
302
303 public:
304 /**
305 */
306 nonvirtual void SetAt (const ForwardIterator& i, ArgByValueType<T> newValue);
307 nonvirtual void SetAt (const BackwardIterator& i, ArgByValueType<T> newValue);
308
309 public:
310 nonvirtual void Invariant () const noexcept;
311
312#if qStroika_Foundation_Debug_AssertionsChecked
313 private:
314 nonvirtual void Invariant_ () const noexcept;
315#endif
316
317 public:
318 template <typename EQUALS_COMPARER = equal_to<T>>
319 [[deprecated ("Since Stroika v3.0d18")]] bool Contains (ArgByValueType<T> item, EQUALS_COMPARER&& equalsComparer) const
320 {
321 return this->Find (item, equalsComparer) != nullptr;
322 }
323 /**
324 * \brief insert the
325 * NB: Can be called if i done, and just means add before the last item (so if i==end() - same as append)
326 */
327 [[deprecated ("Since v3.0d18 - use Insert()")]] void AddBefore (const ForwardIterator& i, ArgByValueType<T> item)
328 {
329 InsertAt (i, item);
330 }
331 [[deprecated ("Since v3.0d18 - use Insert()")]] void AddBefore (const BackwardIterator& i, ArgByValueType<T> item)
332 {
333 InsertAt (i, item);
334 }
335 [[deprecated ("Since v3.0d18 - use Insert()")]] void AddAfter (const ForwardIterator& i, ArgByValueType<T> item)
336 {
337 Insert (i.CurrentIndex () + 1, item);
338 }
339 [[deprecated ("Since v3.0d18 - use Insert()")]] void AddAfter (const BackwardIterator& i, ArgByValueType<T> newValue)
340 {
341 Insert (i.CurrentIndex () + 1, newValue);
342 }
343
344 private:
345 // mostly useful cuz allows for use of realloc, which might imply fewer copies,
346 // but is only legal for trivially_copyable types (cuz realloc sometimes resizes sometimes moves data)
347 static constexpr bool kUseMalloc_{is_trivially_copyable_v<T>};
348
349 private:
350 size_t fLength_{0}; // #items advertised/constructed
351 size_t fSlotsAllocated_{0}; // #items allocated (though not necessarily initialized)
352 T* fItems_{nullptr};
353 };
354
355 /**
356 * IteratorBase<T> is an un-advertised implementation
357 * detail designed to help in source-code sharing among various
358 * iterator implementations.
359 *
360 * \note Design note:
361 * Use index instead of cursored pointer, since performance appears same either way, and
362 * cursored pointer requires patching considerations on 'realloc'.
363 */
364 template <typename T>
365 class Array<T>::IteratorBase {
366 public:
367 // stuff STL requires you to set to look like an iterator
368 using iterator_category = random_access_iterator_tag;
369 using value_type = Array::value_type;
370 using difference_type = ptrdiff_t;
371 using pointer = const value_type*;
372 using reference = const value_type&;
373
374 public:
375 constexpr IteratorBase () noexcept = default;
376 IteratorBase (const Array* data);
377 IteratorBase (const IteratorBase&) noexcept = default;
378
379#if qStroika_Foundation_Debug_AssertionsChecked
380 ~IteratorBase ();
381#endif
382
383 public:
384 nonvirtual const T& operator* () const; // Error to call if AtEnd (), otherwise OK
385
386 public:
387 nonvirtual const T* operator->() const; // Error to call if AtEnd (), otherwise OK
388
389 public:
390 nonvirtual size_t CurrentIndex () const; // NB: This can be called if we are done - if so, it returns size() + 1.
391
392 public:
393 nonvirtual void SetIndex (size_t i);
394
395 public:
396 nonvirtual UnderlyingIteratorRep GetUnderlyingIteratorRep () const;
397
398 public:
399 nonvirtual void SetUnderlyingIteratorRep (const UnderlyingIteratorRep l);
400
401 public:
402 /**
403 * For debugging, assert the iterator data matches argument data
404 */
405 constexpr void AssertDataMatches (const Array* data) const;
406
407 public:
408 nonvirtual void Invariant () const noexcept;
409
410#if qStroika_Foundation_Debug_AssertionsChecked
411 private:
412 nonvirtual void Invariant_ () const noexcept;
413#endif
414
415 protected:
416 const Array* _fData{nullptr};
417 size_t _fCurrentIdx{0};
418
419 private:
420 friend class Array;
421 };
422
423 /**
424 * Use this iterator to iterate forwards over the array. Be careful
425 * not to add or remove things from the array while using this iterator,
426 * since it is not safe.
427 *
428 * \note Satisfies Concepts:
429 * o random_access_iterator<Array<T>::ForwardIterator>
430 * o regular<Array<T>::ForwardIterator> // implies copyable/movable/equality_comparable
431 */
432 template <typename T>
433 class Array<T>::ForwardIterator : public Array<T>::IteratorBase {
434 private:
435 using inherited = IteratorBase;
436
437 public:
438 // forward defs
439 using difference_type = typename inherited::difference_type;
440
441 public:
442 /**
443 * overload taking only 'data' starts at beginning.
444 * note startAt = 0 for begin(), and startAt = data->size () for end
445 */
446 constexpr ForwardIterator () noexcept = default;
447 explicit ForwardIterator (const Array* data, UnderlyingIteratorRep startAt = static_cast<UnderlyingIteratorRep> (0));
448 ForwardIterator (const ForwardIterator&) noexcept = default;
449 constexpr ForwardIterator (ForwardIterator&&) noexcept;
450
451 public:
452 nonvirtual ForwardIterator& operator= (const ForwardIterator&) = default;
453 nonvirtual ForwardIterator& operator= (ForwardIterator&&) noexcept = default;
454
455 public:
456 /**
457 * return true if iterator not AtEnd
458 */
459 explicit operator bool () const;
460
461 public:
462 nonvirtual bool AtStart () const noexcept;
463
464 public:
465 nonvirtual bool AtEnd () const noexcept;
466
467 public:
468 nonvirtual ForwardIterator& operator++ () noexcept;
469 nonvirtual ForwardIterator operator++ (int) noexcept;
470
471 public:
472 nonvirtual ForwardIterator& operator-- () noexcept;
473 nonvirtual ForwardIterator operator-- (int) noexcept;
474
475 public:
476 /**
477 */
478 nonvirtual ForwardIterator operator+ (difference_type i) const;
479
480 public:
481 /**
482 */
483 nonvirtual ForwardIterator operator- (difference_type i) const;
484
485 public:
486 /**
487 */
488 nonvirtual ForwardIterator& operator+= (difference_type i);
489
490 public:
491 /**
492 */
493 nonvirtual ForwardIterator& operator-= (difference_type i);
494
495 public:
496 /**
497 */
498 nonvirtual const T& operator[] (difference_type i) const;
499
500 public:
501 nonvirtual bool operator== (const ForwardIterator& rhs) const;
502 nonvirtual strong_ordering operator<=> (const ForwardIterator& rhs) const;
503
504 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Wnon-template-friend\""); // very tricky to avoid this- tried
505 public:
506 /**
507 * @brief addition of iterator and int is commutative.
508 */
509 friend ForwardIterator operator+ (difference_type i, const ForwardIterator& it);
510
511 public:
512 /**
513 * @brief difference of int and iterator is anti-commutative (so - (it - i))
514 */
515 friend ForwardIterator operator- (difference_type i, const ForwardIterator& it);
516 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Wnon-template-friend\""); // very tricky to avoid this- tried
517
518 private:
519 /**
520 * \note Stroika nearly always defines implementations of functions in the .inl file, but moving friend difference_type operator- there
521 * has been technically difficult, and there isn't a super-strong reason to move it, so - leave it for now.
522 * And just indirect the detailed implementation to the .inl file in a BWA function.
523 *
524 * PROBABLY will need todo similarly for other friend functions in this iterator class.
525 * -- LGP 2026-06-27
526 */
527 static difference_type OPERATOR_MINUS_BWA_ (const ForwardIterator& lhs, const ForwardIterator& rhs);
528
529 public:
530 /**
531 * @brief subtraction of two iterators returns the difference between their positions (handling special cases of sentinal end iterators which are treated as at the end).
532 *
533 * \note Stroika nearly always defines implementations of functions in the .inl file, but moving this definition there
534 * has been technically difficult, and there isn't a super-strong reason to move it, so - leave it here for now.
535 * -- LGP 2026-06-27
536 */
537 friend difference_type operator- (const ForwardIterator& lhs, const ForwardIterator& rhs)
538 {
539 return OPERATOR_MINUS_BWA_ (lhs, rhs);
540 }
541 };
542
543 // see Satisfies Concepts
544 static_assert (random_access_iterator<typename Array<int>::ForwardIterator>);
545 static_assert (regular<typename Array<int>::ForwardIterator>);
546
547 /**
548 * Use this iterator to iterate backwards over the array.
549 *
550 * // NOTE - I THINK NYI (fully) and not used
551 */
552 template <typename T>
553 class Array<T>::BackwardIterator : public Array<T>::IteratorBase {
554 private:
555 using inherited = IteratorBase;
556
557 public:
558 BackwardIterator (const Array* data);
560
561 public:
562 nonvirtual bool AtEnd () const noexcept;
563
564 public:
565 nonvirtual BackwardIterator& operator++ () noexcept;
566
567 public:
568 nonvirtual bool operator== (const BackwardIterator& rhs) const;
569 };
570
571 static_assert (ranges::input_range<Array<int>>); // smoke test - make sure basic iteration etc should work (allows formattable to work)
572
573}
574
575/*
576 ********************************************************************************
577 ***************************** Implementation Details ***************************
578 ********************************************************************************
579 */
580#include "Array.inl"
581
582#endif /*_Stroika_Foundation_Containers_DataStructures_Array_h_ */
nonvirtual void Apply(FUNCTION &&doToElement) const
nonvirtual ForwardIterator Find(FUNCTION &&firstThat) const
nonvirtual void Remove(const ForwardIterator &i)
Definition Array.inl:534
nonvirtual void push_back(ArgByValueType< T > item)
STL-ish alias for Insert (size(), item)
Definition Array.inl:135
nonvirtual void Insert(size_t index, ArgByValueType< T > item)
Definition Array.inl:62
nonvirtual T * data() noexcept
returns internal pointer to data - which is unsynchronized, and only guaranteed valid until the next ...
Definition Array.inl:450
nonvirtual void SetAt(size_t i, ArgByValueType< T > item)
Definition Array.inl:486
nonvirtual void reserve(size_t slotsAlloced)
sets the reserved capacity to slotsAlloced
Definition Array.inl:250
nonvirtual void ReserveAtLeast(size_t slotsAlloced)
Definition Array.inl:319
void AddBefore(const ForwardIterator &i, ArgByValueType< T > item)
insert the NB: Can be called if i done, and just means add before the last item (so if i==end() - sam...
Definition Array.h:327
nonvirtual void SetLength(size_t newLength, ArgByValueType< T > fillValue)
Definition Array.inl:388
nonvirtual ForwardIterator erase(const ForwardIterator &i)
remove the element at i, and return valid iterator to the element that was following it (which can be...
Definition Array.inl:542
NOT a real mutex - just a debugging infrastructure support tool so in debug builds can be assured thr...
conditional_t<(sizeof(CHECK_T)<=2 *sizeof(void *)) and is_trivially_copyable_v< CHECK_T >, CHECK_T, const CHECK_T & > ArgByValueType
This is an alias for 'T' - but how we want to pass it on stack as formal parameter.
Definition TypeHints.h:36
constexpr void AssertDataMatches(const DoublyLinkedList *data) const
nonvirtual ForwardIterator & operator++() noexcept
nonvirtual size_t CurrentIndex(const DoublyLinkedList *data) const
SequencePolicy
equivalent which of 4 types being used std::execution::sequenced_policy, parallel_policy,...