Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Array.h
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. 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 <optional>
10
11#include "Stroika/Foundation/Common/Common.h"
13#include "Stroika/Foundation/Containers/Common.h"
15#include "Stroika/Foundation/Execution/Common.h"
16
17/**
18 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
19 */
20
22
24
25 /**
26 * This class provides an array abstraction, where the size can be set dynamically, and
27 * extra sluff is maintained off the end to reduce copying from reallocs.
28 * Only items 0..size ()-1 are kept constructed. The rest (size()+1
29 * ..fSlotsAlloced) are uninitialized memory. This is important because
30 * it means you can count on DTORs of your T being called when you
31 * remove them from contains, not when the caches happen to empty.
32 *
33 * Array<T> is simple data structure implementation. It is not intended to be directly
34 * used by programmers, except in implementing concrete container reps.
35 *
36 * Array<T> is a template which provides a dynamic array class (very similar to std::vector). Elements
37 * of type T can be assigned, and accessed much like a normal array, except
38 * that when debug is on, accesses are range-checked.
39 *
40 * Array<T> also provides a dynamic sizing capability. It reallocs its
41 * underlying storage is such a ways as to keep a buffer of n(currently 5)%
42 * extra, so that reallocs on resizes only occur log(n) times on n appends.
43 * To save even this space, you can call shrink_to_fit().
44 *
45 * Unlike other dynamic array implementations, when an item is removed,
46 * it is destructed then. So the effects of buffering have no effects on the
47 * semantics of the Array.
48 *
49 *
50 * Notes:
51 *
52 * C++/StandardC arrays and segmented architectures: ((@todo DOCS OBSOLETE))
53 *
54 * Our iterators use address arithmetic since that is faster than
55 * array indexing, but that requires care in the presence of patching,
56 * and in iterating backwards.
57 *
58 * The natural thing to do in iteration would be to have fCurrent
59 * point to the current item, but that would pose difficulties in the
60 * final test at the end of the iteration when iterating backwards. The
61 * final test would be fCurrent < _fStart. This would be illegal in ANSI C.
62 *
63 * The next possible trick is for backwards iteration always point one
64 * past the one you mean, and have *it subtract one before
65 * dereferencing. This works pretty well, but makes source code sharing between
66 * the forwards and backwards cases difficult.
67 *
68 * The next possible trick, and the one we use for now, is to have
69 * fCurrent point to the current item, and in the Next() code, when
70 * going backwards, reset fCurrent to _fEnd - bizarre as this may seem
71 * then the test code can be shared among the forwards and backwards
72 * implementations, all the patching code can be shared, with only this
73 * one minor check. Other potential choices are presented in the TODO
74 * below.
75 *
76 * TODO:
77 * @todo Replace Contains() with Lookup () - as we did for LinkedList<T>
78 * (IN FACT, Find (function) overload basically same thing - maybe have two find overloads - with function and comparer
79 * but easily confusable, and redundant, but maybe useful for performance?)
80 */
81 template <typename T>
83 public:
84 using value_type = T;
85
86 public:
87 /**
88 * Basic (mostly internal) element used by ForwardIterator. Abstract name so can be referenced generically across 'DataStructure' objects
89 */
90 using UnderlyingIteratorRep = size_t;
91
92 public:
93 /**
94 */
95 Array () = default;
96 Array (Array&& from);
97 Array (const Array& from);
98
99 public:
100 ~Array ();
101
102 public:
103 nonvirtual Array& operator= (const Array& rhs);
104
105 public:
106 /**
107 * \note Runtime performance/complexity:
108 * Always: constant
109 */
110 nonvirtual T GetAt (size_t i) const;
111
112 public:
113 /**
114 * Not a great API, since cannot check it very well. However, its more efficient when storing a larger object and you need
115 * to update just part of it.
116 *
117 * \note Runtime performance/complexity:
118 * Always: constant
119 */
120 nonvirtual T* PeekAt (size_t i);
121 nonvirtual const T* PeekAt (size_t i) const;
122
123 public:
124 /**
125 * \note Runtime performance/complexity:
126 * Always: constant
127 */
128 nonvirtual void SetAt (size_t i, ArgByValueType<T> item);
129
130 public:
131 /**
132 * \note Runtime performance/complexity:
133 * Always: constant
134 */
135 nonvirtual T& operator[] (size_t i);
136 nonvirtual T operator[] (size_t i) const;
137
138 public:
139 /**
140 * \note Runtime performance/complexity:
141 * Always: constant
142 */
143 nonvirtual size_t size () const;
144
145 public:
146 /**
147 * \note Runtime performance/complexity:
148 * Always: constant
149 */
150 nonvirtual bool empty () const;
151
152 public:
153 /**
154 * \note Runtime performance/complexity:
155 * Worst Case: O(N)
156 * Typical Case: ?? for small changes often constant, but if enuf change of size O(N) growing. Less shrinking.
157 */
158 nonvirtual void SetLength (size_t newLength, ArgByValueType<T> fillValue);
159
160 public:
161 /**
162 * \note index may == size() - in which case, we are appending.
163 *
164 * \note Runtime performance/complexity:
165 * Worst Case: O(N)
166 * 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
167 */
168 nonvirtual void InsertAt (size_t index, ArgByValueType<T> item);
169
170 public:
171 /**
172 * \brief STL-ish alias for Insert (size(), item)
173 *
174 * @aliases Append
175 *
176 * \note Runtime performance/complexity:
177 * Worst Case: O(N)
178 * Typical: constant
179 */
180 nonvirtual void push_back (ArgByValueType<T> item);
181
182 public:
183 /**
184 * \note Runtime performance/complexity:
185 * Worst Case: O(N)
186 * Typical: depends on index but typically O(N) (can be less if removing from end of Array)
187 */
188 nonvirtual void RemoveAt (size_t index);
189
190 public:
191 /**
192 * \note Runtime performance/complexity:
193 * Worst Case: O(N) - if !trivial_type
194 * Typical: constant
195 */
196 nonvirtual void RemoveAll ();
197
198 public:
199 /**
200 * \note Runtime performance/complexity:
201 * Worst Case/Typical: O(N) ; O(N), but can be less if systematically finding entries near start of array
202 */
203 template <typename EQUALS_COMPARER = equal_to<T>>
204 nonvirtual bool Contains (ArgByValueType<T> item, EQUALS_COMPARER&& equalsComparer) const;
205
206 public:
207 /**
208 * \note Runtime performance/complexity:
209 * Always: O(N)
210 */
211 template <invocable<T> FUNCTION>
212 nonvirtual void Apply (FUNCTION&& doToElement, Execution::SequencePolicy seq = Execution::SequencePolicy::eDEFAULT) const;
213
214 public:
215 class IteratorBase;
216
217 public:
218 class ForwardIterator;
219 class BackwardIterator;
220
221 public:
222 /**
223 */
224 nonvirtual ForwardIterator begin () const;
225
226 public:
227 /**
228 */
229 constexpr ForwardIterator end () const;
230
231 public:
232 /**
233 * Return index of first place in the array matching, or nullopt if not found
234 *
235 * \note Runtime performance/complexity:
236 * Worst Case: O(N)
237 * Typical: O(N), but can be less if systematically finding entries near start of array
238 *
239 * \note in Stroika v2.1, this returned value == size() means not found, but now uses optional to make clearer
240 * and more similar to LinkedList find ...
241 *
242 * \note before Stroika v3.0d10, this returned optional<size_t>
243 */
244 template <predicate<T> FUNCTION>
245 nonvirtual ForwardIterator Find (FUNCTION&& firstThat) const;
246
247 public:
248 /*
249 * Memory savings/optimization methods. Use this to tune usage
250 * of arrays so that they don't waste time in Realloc's.
251 */
252 nonvirtual size_t capacity () const;
253
254 public:
255 nonvirtual void reserve (size_t slotsAlloced);
256
257 public:
258 nonvirtual void shrink_to_fit ();
259
260 public:
261 /*
262 * Support for COW (CopyOnWrite):
263 *
264 * Take iterator 'pi' which is originally a valid iterator from 'movedFrom' - and replace *pi with a valid
265 * iterator from 'this' - which points at the same logical position. This requires that this container
266 * was just 'copied' from 'movedFrom' - and is used to produce an equivalent iterator (since iterators are tied to
267 * the container they were iterating over).
268 */
269 nonvirtual void MoveIteratorHereAfterClone (IteratorBase* pi, const Array* movedFrom) const;
270
271 public:
272 /**
273 * \see erase () - same as Remove(it) but returns iterator of 'next'
274 */
275 nonvirtual void Remove (const ForwardIterator& i);
276 nonvirtual void Remove (const BackwardIterator& i);
277
278 public:
279 /**
280 * \brief remove the element at i, and return valid iterator to the element that was following it (which can be empty iterator)
281 *
282 * \pre i != end ()
283 *
284 * \brief see https://en.cppreference.com/w/cpp/container/vector/erase
285 */
286 nonvirtual ForwardIterator erase (const ForwardIterator& i);
287
288 public:
289 nonvirtual void SetAt (const ForwardIterator& i, ArgByValueType<T> newValue);
290 nonvirtual void SetAt (const BackwardIterator& i, ArgByValueType<T> newValue);
291
292 public:
293 // NB: Can be called if i done
294 nonvirtual void AddBefore (const ForwardIterator& i, ArgByValueType<T> item);
295 nonvirtual void AddBefore (const BackwardIterator& i, ArgByValueType<T> item);
296
297 public:
298 nonvirtual void AddAfter (const ForwardIterator& i, ArgByValueType<T> item);
299 nonvirtual void AddAfter (const BackwardIterator& i, ArgByValueType<T> item);
300
301 public:
302 nonvirtual void Invariant () const noexcept;
303
304#if qStroika_Foundation_Debug_AssertionsChecked
305 private:
306 nonvirtual void Invariant_ () const noexcept;
307#endif
308
309 private:
310 // mostly useful cuz allows for use of realloc, which might imply fewer copies
311 static constexpr bool kUseMalloc_{is_trivially_copyable_v<T>};
312
313 private:
314 size_t fLength_{0}; // #items advertised/constructed
315 size_t fSlotsAllocated_{0}; // #items allocated (though not necessarily initialized)
316 T* fItems_{nullptr};
317 };
318
319 /**
320 * IteratorBase<T> is an un-advertised implementation
321 * detail designed to help in source-code sharing among various
322 * iterator implementations.
323 *
324 * \note Design note:
325 * Use index instead of cursored pointer, since performance appears same either way, and
326 * cursored pointer requires patching considerations on 'realloc'.
327 */
328 template <typename T>
329 class Array<T>::IteratorBase {
330 public:
331 // stuff STL requires you to set to look like an iterator
332 using iterator_category = forward_iterator_tag;
333 using value_type = Array::value_type;
334 using difference_type = ptrdiff_t;
335 using pointer = const value_type*;
336 using reference = const value_type&;
337
338 public:
339 constexpr IteratorBase () noexcept = default;
340 IteratorBase (const Array* data);
341 IteratorBase (const IteratorBase&) noexcept = default;
342
343#if qStroika_Foundation_Debug_AssertionsChecked
344 ~IteratorBase ();
345#endif
346
347 public:
348 nonvirtual const T& operator* () const; // Error to call if Done (), otherwise OK
349
350 public:
351 nonvirtual const T* operator->() const; // Error to call if Done (), otherwise OK
352
353 public:
354 nonvirtual size_t CurrentIndex () const; // NB: This can be called if we are done - if so, it returns size() + 1.
355
356 public:
357 nonvirtual void SetIndex (size_t i);
358
359 public:
360 nonvirtual bool operator== (const IteratorBase& rhs) const;
361
362 public:
363 nonvirtual UnderlyingIteratorRep GetUnderlyingIteratorRep () const;
364
365 public:
366 nonvirtual void SetUnderlyingIteratorRep (const UnderlyingIteratorRep l);
367
368 public:
369 /**
370 * For debugging, assert the iterator data matches argument data
371 */
372 constexpr void AssertDataMatches (const Array* data) const;
373
374 public:
375 nonvirtual void Invariant () const noexcept;
376
377#if qStroika_Foundation_Debug_AssertionsChecked
378 private:
379 nonvirtual void Invariant_ () const noexcept;
380#endif
381
382 protected:
383 const Array* _fData{nullptr};
384 size_t _fCurrentIdx{0};
385
386 private:
387 friend class Array;
388 };
389
390 /**
391 * Use this iterator to iterate forwards over the array. Be careful
392 * not to add or remove things from the array while using this iterator,
393 * since it is not safe.
394 */
395 template <typename T>
396 class Array<T>::ForwardIterator : public Array<T>::IteratorBase {
397 private:
398 using inherited = IteratorBase;
399
400 public:
401 /**
402 * overload taking only 'data' starts at beginning.
403 * note startAt = 0 for begin(), and startAt = data->size () for end
404 */
405 constexpr ForwardIterator () noexcept = default;
406 explicit ForwardIterator (const Array* data, UnderlyingIteratorRep startAt = static_cast<UnderlyingIteratorRep> (0));
407 ForwardIterator (const ForwardIterator&) noexcept = default;
408 constexpr ForwardIterator (ForwardIterator&&) noexcept;
409
410 public:
411 nonvirtual ForwardIterator& operator= (const ForwardIterator&) = default;
412 nonvirtual ForwardIterator& operator= (ForwardIterator&&) noexcept = default;
413
414 public:
415 /**
416 * return true if iterator not Done
417 */
418 explicit operator bool () const;
419
420 public:
421 nonvirtual bool Done () const noexcept;
422
423 public:
424 nonvirtual ForwardIterator& operator++ () noexcept;
425 nonvirtual ForwardIterator operator++ (int) noexcept;
426 };
427
428 /**
429 * Use this iterator to iterate backwards over the array. Be careful
430 * not to add or remove things from the array while using this iterator,
431 * since it is not safe. Use BackwardIterator_Patch for those cases.
432 *
433 * // NOTE - I THINK NYI (fully) and not used
434 */
435 template <typename T>
436 class Array<T>::BackwardIterator : public Array<T>::IteratorBase {
437 private:
438 using inherited = IteratorBase;
439
440 public:
441 BackwardIterator (const Array* data);
442 BackwardIterator (const Array* data, UnderlyingIteratorRep startAt);
443
444 public:
445 nonvirtual bool Done () const noexcept;
446
447 public:
448 nonvirtual BackwardIterator& operator++ () noexcept;
449 };
450
451 static_assert (ranges::input_range<Array<int>>); // smoke test - make sure basic iteration etc should work (allows formattable to work)
452
453}
454
455/*
456 ********************************************************************************
457 ***************************** Implementation Details ***************************
458 ********************************************************************************
459 */
460#include "Array.inl"
461
462#endif /*_Stroika_Foundation_Containers_DataStructures_Array_h_ */
nonvirtual void Apply(FUNCTION &&doToElement, Execution::SequencePolicy seq=Execution::SequencePolicy::eDEFAULT) const
nonvirtual ForwardIterator Find(FUNCTION &&firstThat) const
nonvirtual void Remove(const ForwardIterator &i)
Definition Array.inl:452
nonvirtual void push_back(ArgByValueType< T > item)
STL-ish alias for Insert (size(), item)
Definition Array.inl:92
nonvirtual bool Contains(ArgByValueType< T > item, EQUALS_COMPARER &&equalsComparer) const
nonvirtual void InsertAt(size_t index, ArgByValueType< T > item)
Definition Array.inl:60
nonvirtual void SetAt(size_t i, ArgByValueType< T > item)
Definition Array.inl:404
nonvirtual void SetLength(size_t newLength, ArgByValueType< T > fillValue)
Definition Array.inl:308
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:460
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:32
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,...