1 #pragma once
2 
3 /// \file Array.h
4 /// \brief Generic dynamically allocated resizable storage
5 /// \author Pavel Sevecek (sevecek at sirrah.troja.mff.cuni.cz)
6 /// \date 2016-2021
7 
8 #include "math/MathBasic.h"
9 #include "objects/containers/ArrayView.h"
10 #include "objects/containers/BasicAllocators.h"
11 
12 #ifdef SPH_GCC
13 #pragma GCC diagnostic ignored "-Wstringop-overflow"
14 #endif
15 
16 NAMESPACE_SPH_BEGIN
17 
18 template <typename T, typename TAllocator = Mallocator, typename TCounter = Size>
19 class CopyableArray;
20 
21 /// \brief Helper class, used to avoid including large header limits.h
22 template <typename TValue>
23 struct NumericLimits;
24 
25 template <>
26 struct NumericLimits<uint64_t> {
27     static constexpr uint64_t max() {
28         return uint64_t(-1);
29     }
30 };
31 
32 template <>
33 struct NumericLimits<uint32_t> {
34     static constexpr uint32_t max() {
35         return uint32_t(-1);
36     }
37 };
38 
39 /// \brief Generic dynamically allocated resizable storage.
40 ///
41 /// Can also be used with STL algorithms.
42 template <typename T, typename TAllocator = Mallocator, typename TCounter = Size>
43 class Array : private TAllocator, public Noncopyable {
44 private:
45     using StorageType = typename WrapReferenceType<T>::Type;
46     StorageType* data = nullptr;
47     TCounter actSize = 0;
48     TCounter maxSize = 0;
49 
50     static constexpr TCounter maxValue = NumericLimits<TCounter>::max();
51 
52 public:
53     using Type = T;
54     using Counter = TCounter;
55 
56     Array() = default;
57 
58     /// \brief Constructs array of given size.
59     ///
60     /// \param elementCnt Number of elements to be constructed (using default constructor)
61     /// \param allocatedSize Number of allocated elements.
62     explicit Array(const TCounter elementCnt, const TCounter allocatedSize = maxValue) {
63         this->alloc(elementCnt, allocatedSize);
64 
65         // emplace elements
66         if (!std::is_trivially_default_constructible<T>::value) {
67             for (TCounter i = 0; i < actSize; ++i) {
68                 new (data + i) StorageType();
69             }
70         }
71     }
72 
73     /// \brief Constructs array from initialized list.
74     ///
75     /// Allocate only enough elements to store the list. Elements are constructed using copy constructor of
76     /// stored type.
77     Array(std::initializer_list<StorageType> list) {
78         actSize = TCounter(list.size());
79         maxSize = actSize;
80         MemoryBlock block = TAllocator::allocate(maxSize * sizeof(StorageType), alignof(StorageType));
81         SPH_ASSERT(block.ptr);
82         data = (StorageType*)block.ptr;
83         TCounter i = 0;
84         for (auto& l : list) {
85             new (data + i) StorageType(l);
86             i++;
87         }
88     }
89 
90     /// \brief Move constructor from array of the same type
91     Array(Array&& other) {
92         std::swap(data, other.data);
93         std::swap(maxSize, other.maxSize);
94         std::swap(actSize, other.actSize);
95     }
96 
97     ~Array() {
98         // explicitly destroy all constructed elements
99         if (!std::is_trivially_destructible<T>::value) {
100             for (TCounter i = 0; i < actSize; ++i) {
101                 data[i].~StorageType();
102             }
103         }
104         if (data) {
105             MemoryBlock block(data, maxSize * sizeof(StorageType));
106             TAllocator::deallocate(block);
107             data = nullptr;
108         }
109     }
110 
111     Array& operator=(Array&& other) {
112         std::swap(data, other.data);
113         std::swap(maxSize, other.maxSize);
114         std::swap(actSize, other.actSize);
115         return *this;
116     }
117 
118     /// \brief Performs deep-copy of array elements, resizing array if needed.
119     ///
120     /// This is only way to copy elements, as for Array object deep-copying of elements is forbidden as it is
121     /// rarely needed and deleting copy constructor helps us avoid accidental deep-copy, for example when
122     /// passing array as an argument of function.
123     Array& operator=(const CopyableArray<T, TAllocator, TCounter>& other) {
124         const Array& rhs = other;
125         this->resize(rhs.size());
126         for (TCounter i = 0; i < rhs.size(); ++i) {
127             data[i] = rhs[i];
128         }
129         return *this;
130     }
131 
132     /// \brief For l-value references assign each value (does not actually move anything).
133     ///
134     /// Works only for arrays of same size, for simplicity.
135     template <typename U, typename = std::enable_if_t<std::is_lvalue_reference<T>::value, U>>
136     Array& operator=(Array<U>&& other) {
137         SPH_ASSERT(this->size() == other.size());
138         for (TCounter i = 0; i < other.size(); ++i) {
139             (*this)[i] = std::forward<U>(other[i]);
140         }
141         return *this;
142     }
143 
144     /// \brief Performs a deep copy of all elements of the array.
145     ///
146     /// All elements are created using copy-constructor.
147     Array clone() const {
148         Array newArray;
149         newArray.reserve(actSize);
150         for (const T& value : *this) {
151             newArray.emplaceBack(value);
152         }
153         return newArray;
154     }
155 
156     INLINE T& operator[](const TCounter idx) noexcept {
157         SPH_ASSERT(unsigned(idx) < unsigned(actSize), idx, actSize);
158         return data[idx];
159     }
160 
161     INLINE const T& operator[](const TCounter idx) const noexcept {
162         SPH_ASSERT(unsigned(idx) < unsigned(actSize), idx, actSize);
163         return data[idx];
164     }
165 
166     INLINE T& front() noexcept {
167         SPH_ASSERT(actSize > 0);
168         return data[0];
169     }
170 
171     INLINE const T& front() const noexcept {
172         SPH_ASSERT(actSize > 0);
173         return data[0];
174     }
175 
176     INLINE T& back() noexcept {
177         SPH_ASSERT(actSize > 0);
178         return data[actSize - 1];
179     }
180 
181     INLINE const T& back() const noexcept {
182         SPH_ASSERT(actSize > 0);
183         return data[actSize - 1];
184     }
185 
186     /// \brief Sets all elements of the array to given value.
187     void fill(const T& t) {
188         for (auto& v : *this) {
189             v = t;
190         }
191     }
192 
193     INLINE TCounter size() const noexcept {
194         return actSize;
195     }
196 
197     INLINE TCounter capacity() const noexcept {
198         return maxSize;
199     }
200 
201     INLINE bool empty() const noexcept {
202         return actSize == 0;
203     }
204 
205     /// \brief Resizes the array to new size.
206     ///
207     /// This potentially allocated more memory than required, to speed up the allocations. If the new size is
208     /// bigger than the current size, new elements are created using default constructor, all currently stored
209     /// values (within interval [0, newSize-1]) are preserved, possibly moved using their move constructor.
210     /// If the new size is lower than the current size, elements at the end of the array are destroyed.
211     /// However, the array is not reallocated, the size is kept for future growth.
212     ///
213     /// \attention This invalidates all references, pointers, iterators, array views, etc. pointed to the
214     /// elements of the array.
215     void resize(const TCounter newSize) {
216         // check suspiciously high values
217         SPH_ASSERT(newSize < (NumericLimits<TCounter>::max() >> 1));
218         if (newSize <= maxSize) {
219             // enough elements is already allocated
220             if (newSize >= actSize) {
221                 // enlarging array, we need to construct some new elements
222                 if (!std::is_trivially_default_constructible<T>::value) {
223                     for (TCounter i = actSize; i < newSize; ++i) {
224                         new (data + i) StorageType();
225                     }
226                 }
227             } else {
228                 if (!std::is_trivially_destructible<T>::value) {
229                     // shrinking array, we need to delete some elements
230                     for (TCounter i = newSize; i < actSize; ++i) {
231                         data[i].~StorageType();
232                     }
233                 }
234             }
235         } else {
236             // requested more elements than allocate, need to reallocated.
237             // allocate twice the current number or the new size, whatever is higher, to avoid frequent
238             // reallocation when pushing elements one by one
239             const TCounter actNewSize = max(2 * maxSize, newSize);
240             Array newArray(0, actNewSize);
241             // copy all elements into the new array, using move constructor
242             for (TCounter i = 0; i < actSize; ++i) {
243                 new (newArray.data + i) StorageType(std::move(data[i]));
244             }
245             // default-construct new elements
246             if (!std::is_trivially_default_constructible<T>::value) {
247                 for (TCounter i = actSize; i < newSize; ++i) {
248                     new (newArray.data + i) StorageType();
249                 }
250             }
251             // move the array into this
252             *this = std::move(newArray);
253         }
254         actSize = newSize;
255     }
256 
257     /// \brief Resizes the array to new size and assigns a given value to all newly created elements.
258     ///
259     /// Previously stored elements are not modified, they are possibly moved using their move operator.
260     /// If the new size is lower than the current size, elements at the end are destroyed; the given value is
261     /// then irrelevant.
262     ///
263     /// \attention This invalidates all references, pointers, iterators, array views, etc. pointed to the
264     /// elements of the array.
265     void resizeAndSet(const TCounter newSize, const T& value) {
266         const TCounter currentSize = actSize;
267         this->resize(newSize);
268         for (TCounter i = currentSize; i < actSize; ++i) {
269             (*this)[i] = value;
270         }
271     }
272 
273     /// \brief Allocates enough memory to store the given number of elements.
274     ///
275     ///  If enough memory is already allocated, the function does nothing.
276     ///
277     /// \attention This invalidates all references, pointers, iterators, array views, etc. pointed to the
278     /// elements of the array.
279     void reserve(const TCounter newMaxSize) {
280         SPH_ASSERT(newMaxSize < (NumericLimits<TCounter>::max() >> 1));
281         if (newMaxSize > maxSize) {
282             const TCounter actNewSize = max(2 * maxSize, newMaxSize);
283             Array newArray;
284             // don't use the Array(0, actNewSize) constructor to allow using emplaceBack for types without
285             // default constructor
286             newArray.alloc(0, actNewSize);
287             // copy all elements into the new array, using move constructor
288             for (TCounter i = 0; i < actSize; ++i) {
289                 new (newArray.data + i) StorageType(std::move(data[i]));
290             }
291             newArray.actSize = actSize;
292             // move the array into this
293             *this = std::move(newArray);
294         }
295     }
296 
297     /// \brief Reallocates the array, removing the unused elements to save memory.
298     void shrink() {
299         Array newArray;
300         newArray.pushAll(std::move(*this));
301         *this = std::move(newArray);
302     }
303 
304     /// \brief Adds new element to the end of the array, resizing the array if necessary.
305     template <typename U>
306     INLINE void push(U&& u) {
307         resize(actSize + 1);
308         data[actSize - 1] = std::forward<U>(u);
309     }
310 
311     template <typename TIter>
312     void pushAll(const TIter first, const TIter last) {
313         for (TIter iter = first; iter != last; ++iter) {
314             push(*iter);
315         }
316     }
317 
318     void pushAll(const Array& other) {
319         reserve(actSize + other.size());
320         pushAll(other.cbegin(), other.cend());
321     }
322 
323     void pushAll(Array&& other) {
324         reserve(actSize + other.size());
325         for (T& value : other) {
326             push(std::move(value));
327         }
328     }
329 
330     /// \brief Constructs a new element at the end of the array in place, using the provided arguments.
331     template <typename... TArgs>
332     StorageType& emplaceBack(TArgs&&... args) {
333         reserve(actSize + 1);
334         SPH_ASSERT(maxSize > actSize);
335         StorageType* ptr = new (data + actSize) StorageType(std::forward<TArgs>(args)...);
336         SPH_ASSERT(ptr);
337         actSize++;
338         return *ptr;
339     }
340 
341     /// \brief Inserts a new element to given position in the array.
342     ///
343     /// All the existing elements after the given positions are moved using move operator.
344     template <typename U>
345     void insert(const TCounter position, U&& value) {
346         SPH_ASSERT(position <= actSize);
347         this->resize(actSize + 1);
348         std::move_backward(this->begin() + position, this->end() - 1, this->end());
349         data[position] = std::forward<U>(value);
350     }
351 
352     /// \brief Inserts a range of values into the array, starting at given position.
353     ///
354     /// This has the same effect as calling \ref insert for every element in the range. All the existing
355     /// elements after the given positions are moved using move operator.
356     template <typename TIterator>
357     void insert(const TCounter position, const TIterator first, const TIterator last) {
358         SPH_ASSERT(position <= actSize);
359         if (SPH_UNLIKELY(first == last)) {
360             // inserting an empty range
361             return;
362         }
363         const TCounter count = TCounter(last - first);
364         this->resize(actSize + count);
365         std::move_backward(this->begin() + position, this->end() - count, this->end());
366         Size i = position;
367         for (TIterator iter = first; iter != last; ++iter) {
368             data[i++] = *iter;
369         }
370     }
371 
372     /// \brief Removes the last element from the array and return its value.
373     ///
374     /// Asserts if the array is empty.
375     INLINE T pop() {
376         SPH_ASSERT(actSize > 0);
377         T value = data[actSize - 1];
378         resize(actSize - 1);
379         return value;
380     }
381 
382     /// \brief Removes an element with given index from the array.
383     void remove(const TCounter idx) {
384         SPH_ASSERT(idx < actSize);
385         for (TCounter i = idx; i < actSize - 1; ++i) {
386             data[i] = std::move(data[i + 1]);
387         }
388         resize(actSize - 1);
389     }
390 
391     /// \brief Removes elements specified by indices from the array.
392     ///
393     /// This is effectively the same as calling \ref remove with each index separately. The given array of
394     /// indices must be sorted (from smallest to largest), checked by assert.
395     void remove(const ArrayView<const TCounter> idxs) {
396         Size shift = 0;
397         if (SPH_UNLIKELY(idxs.empty())) {
398             return;
399         }
400 
401         // move all elements between indices
402         for (Size k = 0; k < idxs.size() - 1; ++k) {
403             SPH_ASSERT(idxs[k] < idxs[k + 1]);
404             for (TCounter i = idxs[k]; i < idxs[k + 1] - 1; ++i) {
405                 data[i - shift] = std::move(data[i + 1]);
406             }
407             shift++;
408         }
409         // move all elements after last index
410         for (TCounter i = idxs.back(); i < actSize - 1; ++i) {
411             data[i - shift] = std::move(data[i + 1]);
412         }
413 
414         resize(actSize - idxs.size());
415     }
416 
417     /// \brief Removes all elements in given range.
418     template <typename TIter>
419     void remove(TIter first, TIter last) {
420         SPH_ASSERT(first <= last);
421         if (SPH_UNLIKELY(first == last)) {
422             return;
423         }
424         const Size count = last - first;
425         SPH_ASSERT(Size(first - begin()) + count <= actSize);
426 
427         for (TIter iter = first; iter != end() - count; ++iter) {
428             *iter = std::move(*(iter + count));
429         }
430         resize(actSize - count);
431     }
432 
433     /// \brief Removes all elements from the array, but does NOT release the memory.
434     void clear() {
435         if (!std::is_trivially_destructible<T>::value) {
436             for (TCounter i = 0; i < actSize; ++i) {
437                 data[i].~StorageType();
438             }
439         }
440         actSize = 0;
441     }
442 
443     /// \brief Swaps content of two arrays
444     void swap(Array& other) {
445         std::swap(data, other.data);
446         std::swap(maxSize, other.maxSize);
447         std::swap(actSize, other.actSize);
448     }
449 
450     INLINE Iterator<StorageType> begin() noexcept {
451         return Iterator<StorageType>(data, data, data + actSize);
452     }
453 
454     INLINE Iterator<const StorageType> begin() const noexcept {
455         return Iterator<const StorageType>(data, data, data + actSize);
456     }
457 
458     INLINE Iterator<const StorageType> cbegin() const noexcept {
459         return Iterator<const StorageType>(data, data, data + actSize);
460     }
461 
462     INLINE Iterator<StorageType> end() noexcept {
463         return Iterator<StorageType>(data + actSize, data, data + actSize);
464     }
465 
466     INLINE Iterator<const StorageType> end() const noexcept {
467         return Iterator<const StorageType>(data + actSize, data, data + actSize);
468     }
469 
470     INLINE Iterator<const StorageType> cend() const noexcept {
471         return Iterator<const StorageType>(data + actSize, data, data + actSize);
472     }
473 
474     /// \brief Returns the interface to the allocator.
475     const TAllocator& allocator() const {
476         return *this;
477     }
478 
479     /// \brief Returns the interface to the allocator.
480     TAllocator& allocator() {
481         return *this;
482     }
483 
484     /// \brief  Implicit conversion to arrayview.
485     INLINE operator ArrayView<T, TCounter>() noexcept {
486         return ArrayView<T, TCounter>(data, actSize);
487     }
488 
489     /// \brief Implicit conversion to arrayview, const version.
490     INLINE operator ArrayView<const T, TCounter>() const noexcept {
491         return ArrayView<const T, TCounter>(data, actSize);
492     }
493 
494     /// \brief Explicit conversion to arrayview
495     ArrayView<T, TCounter> view() noexcept {
496         return ArrayView<T, TCounter>(data, actSize);
497     }
498 
499     /// \brief Explicit conversion to arrayview, const version
500     ArrayView<const T, TCounter> view() const noexcept {
501         return ArrayView<const T, TCounter>(data, actSize);
502     }
503 
504     /// \brief Comparison operator, comparings array element-by-element.
505     ///
506     /// If arrays differ in number of constructed elements, the comparison always returns false; allocated
507     /// size does not play role here.
508     bool operator==(const Array& other) const noexcept {
509         return view() == other.view();
510     }
511 
512     /// \brief Inequality operator
513     bool operator!=(const Array& other) const noexcept {
514         return view() != other.view();
515     }
516 
517 private:
518     void alloc(const TCounter elementCnt, const TCounter allocatedSize) {
519         actSize = elementCnt;
520         maxSize = allocatedSize;
521         if (allocatedSize == maxValue) {
522             maxSize = actSize;
523         }
524         // allocate maxSize elements
525         MemoryBlock block = TAllocator::allocate(maxSize * sizeof(StorageType), alignof(StorageType));
526         SPH_ASSERT(block.ptr);
527         data = (StorageType*)block.ptr;
528     }
529 };
530 
531     /// \brief Prints content of array to stream.
532 ///
533 /// Enabled only if the stored type has overloaded << operator.
534 template <typename TStream, typename T, typename = std::enable_if_t<HasStreamOperator<T, TStream>::value>>
535 TStream& operator<<(TStream& stream, const Array<T>& array) {
536     for (const T& t : array) {
537         stream << t << std::endl;
538     }
539     return stream;
540 }
541 
542 
543 template <typename T, typename TAllocator, typename TCounter>
544 class CopyableArray {
545 private:
546     const Array<T, TAllocator, TCounter>& array;
547 
548 public:
549     CopyableArray(const Array<T, TAllocator, TCounter>& array)
550         : array(array) {}
551 
552     operator const Array<T, TAllocator, TCounter>&() const {
553         return array;
554     }
555 };
556 
557 /// \todo test
558 template <typename T, typename TAllocator, typename TCounter>
559 INLINE CopyableArray<T, TAllocator, TCounter> copyable(const Array<T, TAllocator, TCounter>& array) {
560     return CopyableArray<T, TAllocator, TCounter>(array);
561 }
562 
563 /// \brief Creates an array from a list of parameters.
564 template <typename T0, typename... TArgs>
565 Array<std::decay_t<T0>> makeArray(T0&& t0, TArgs&&... rest) {
566     return Array<std::decay_t<T0>>{ std::forward<T0>(t0), std::forward<TArgs>(rest)... };
567 }
568 
569 /// \brief Creates a l-value reference array from a list of l-value references.
570 template <typename T0, typename... TArgs>
571 Array<T0&> tieToArray(T0& t0, TArgs&... rest) {
572     return Array<T0&>{ t0, rest... };
573 }
574 
575 
576 NAMESPACE_SPH_END
577 
578 /// Overload of std::swap for Sph::Array.
579 namespace std {
580 template <typename T, typename TCounter>
581 void swap(Sph::Array<T, TCounter>& ar1, Sph::Array<T, TCounter>& ar2) {
582     ar1.swap(ar2);
583 }
584 } // namespace std
585