1 #pragma once
2 
3 #include "objects/containers/ArrayView.h"
4 #include "objects/wrappers/AlignedStorage.h"
5 #include <initializer_list>
6 
7 NAMESPACE_SPH_BEGIN
8 
9 const struct EmptyArray {
10 } EMPTY_ARRAY;
11 
12 /// \brief Array with fixed number of allocated elements.
13 ///
14 /// Number of actually created elements can be different and it can also be changed; elements can be created
15 /// and destroyed, although it is not possible to construct more elements than the maximum allocated number,
16 /// determined by template parameter N or function \ref maxSize. The array is intentionally non-copyable to
17 /// avoid accidentaly copying values, where move or pass-by-reference is possible.
18 template <typename T, int N, typename TCounter = Size>
19 class StaticArray {
20 private:
21     AlignedStorage<T> data[N];
22     TCounter actSize;
23 
24     using StorageType = WrapReference<T>;
25 
26 public:
27     /// \brief Default constructor, calls default constructor on all elements.
StaticArray()28     StaticArray() {
29         if (!std::is_trivially_default_constructible<T>::value) {
30             for (TCounter i = 0; i < N; ++i) {
31                 data[i].emplace();
32             }
33         }
34         actSize = N;
35     }
36 
37     /// \brief Initialize an empty array.
38     ///
39     /// This effectively creates an empty stack; all N elements are allocated, but none is constructed and
40     /// size of the array is zero.
StaticArray(const EmptyArray &)41     StaticArray(const EmptyArray&) {
42         actSize = 0;
43     }
44 
45     /// \brief Initialize using initializer_list.
46     ///
47     /// The size of the list must be smaller or equal to N. All N elements are all allocated, elements are
48     /// constructed from initializer_list using copy constructor.
StaticArray(std::initializer_list<StorageType> list)49     StaticArray(std::initializer_list<StorageType> list) {
50         actSize = 0;
51         SPH_ASSERT(list.size() <= N);
52         for (auto& i : list) {
53             data[actSize++].emplace(i);
54         }
55     }
56 
57     StaticArray(const StaticArray& other) = delete;
58 
StaticArray(StaticArray && other)59     StaticArray(StaticArray&& other) {
60         actSize = 0;
61         for (TCounter i = 0; i < other.size(); ++i) {
62             // move if it contains values, just copy if it contains references
63             data[actSize++].emplace(std::forward<T>(other[i]));
64         }
65     }
66 
67     /// \brief Destructor, destroys all constructed elements in the array.
~StaticArray()68     ~StaticArray() {
69         if (!std::is_trivially_destructible<T>::value) {
70             for (TCounter i = 0; i < actSize; ++i) {
71                 data[i].destroy();
72             }
73         }
74         actSize = 0;
75     }
76 
77     StaticArray& operator=(const StaticArray& other) = delete;
78 
79     /// \brief Move operator for arrays holding default-constructible types.
80     ///
81     /// Resizes array and moves all elements of the rhs array.
82     template <typename U, typename = std::enable_if_t<std::is_default_constructible<T>::value, U>>
83     StaticArray& operator=(StaticArray<U, N>&& other) {
84         this->resize(other.size());
85         for (TCounter i = 0; i < other.size(); ++i) {
86             // move if it contains values, just copy if it contains references
87             (*this)[i] = std::forward<T>(other[i]);
88         }
89         return *this;
90     }
91 
92     /// \brief Special assignment operator for array of references on left-hand side.
93     ///
94     /// Can be used similarly to std::tie; a function may return multiple values by wrapping them into
95     /// StaticArray<T>, these values can be then saved individual variables by wrapping the variables into
96     /// \ref StaticArray<T&>. A big advantage over tuple is the option to return a variable number of elements
97     /// (i.e. number of arguments is not known at compile time).
98     template <typename U, int M, typename = std::enable_if_t<std::is_lvalue_reference<T>::value, U>>
99     StaticArray& operator=(StaticArray<U, M>&& other) {
100         SPH_ASSERT(this->size() == other.size());
101         for (TCounter i = 0; i < other.size(); ++i) {
102             (*this)[i] = std::forward<U>(other[i]);
103         }
104         return *this;
105     }
106 
107     /// \brief Clones the array, calling copy constructor on all elements.
108     ///
109     /// The size of the cloned array corresponds to current size of this array.
clone()110     StaticArray clone() const {
111         StaticArray cloned(EMPTY_ARRAY);
112         for (TCounter i = 0; i < actSize; ++i) {
113             cloned.push(data[i].get());
114         }
115         return cloned;
116     }
117 
118     /// \brief Assigns a value to all constructed elements of the array.
119     ///
120     /// Does not resize the array.
fill(const T & value)121     void fill(const T& value) {
122         for (TCounter i = 0; i < actSize; ++i) {
123             data[i].get() = value;
124         }
125     }
126 
127     /// \brief Returns the element with given index.
128     INLINE T& operator[](const TCounter idx) noexcept {
129         SPH_ASSERT(idx >= 0 && idx < actSize, idx, actSize);
130         return data[idx].get();
131     }
132 
133     /// \brief Returns the element with given index.
134     INLINE const T& operator[](const TCounter idx) const noexcept {
135         SPH_ASSERT(idx >= 0 && idx < actSize, idx, actSize);
136         return data[idx].get();
137     }
138 
139     /// \brief Returns the maximum allowed size of the array.
maxSize()140     INLINE constexpr TCounter maxSize() const noexcept {
141         return N;
142     }
143 
144     /// \brief Returns the current size of the array (number of constructed elements).
145     ///
146     /// Can be between 0 and N.
size()147     INLINE TCounter size() const {
148         return actSize;
149     }
150 
151     /// \brief Return true if the array is empty.
152     ///
153     /// Depends only on number of constructed elements, not allocated size.
empty()154     INLINE bool empty() const {
155         return actSize == 0;
156     }
157 
158     /// \brief Inserts a value to the end of the array using copy/move constructor.
159     ///
160     /// The current size of the array must be less than N, checked by assert.
161     template <typename U, typename = std::enable_if_t<std::is_constructible<StorageType, U>::value>>
push(U && value)162     INLINE void push(U&& value) {
163         SPH_ASSERT(actSize < N);
164         data[actSize++].emplace(std::forward<U>(value));
165     }
166 
167     /// \brief Removes and destroys element from the end of the array.
168     ///
169     /// The removed element is returned from the function. Array must not be empty, checked by assert.
pop()170     INLINE T pop() {
171         SPH_ASSERT(actSize > 0);
172         T value = data[actSize - 1];
173         data[actSize - 1].destroy();
174         actSize--;
175         return value;
176     }
177 
178     /// \brief Changes size of the array.
179     ///
180     /// New size must be between 0 and N. If the array is shrinked, elements from the end of the array are
181     /// destroyed; if the array is enlarged, new elements are created using default constructor.
resize(const TCounter newSize)182     void resize(const TCounter newSize) {
183         SPH_ASSERT(unsigned(newSize) <= N);
184         if (!std::is_trivially_default_constructible<T>::value) {
185             if (newSize > actSize) {
186                 for (TCounter i = actSize; i < newSize; ++i) {
187                     data[i].emplace();
188                 }
189             } else {
190                 for (TCounter i = newSize; i < actSize; ++i) {
191                     data[i].destroy();
192                 }
193             }
194         }
195         actSize = newSize;
196     }
197 
begin()198     INLINE Iterator<StorageType> begin() {
199         return Iterator<StorageType>(rawData(), rawData(), rawData() + actSize);
200     }
201 
begin()202     INLINE Iterator<const StorageType> begin() const {
203         return Iterator<const StorageType>(rawData(), rawData(), rawData() + actSize);
204     }
205 
cbegin()206     INLINE Iterator<const StorageType> cbegin() const {
207         return Iterator<const StorageType>(rawData(), rawData(), rawData() + actSize);
208     }
209 
end()210     INLINE Iterator<StorageType> end() {
211         return Iterator<StorageType>(rawData() + actSize, rawData(), rawData() + actSize);
212     }
213 
end()214     INLINE Iterator<const StorageType> end() const {
215         return Iterator<const StorageType>(rawData() + actSize, rawData(), rawData() + actSize);
216     }
217 
cend()218     INLINE Iterator<const StorageType> cend() const {
219         return Iterator<const StorageType>(rawData() + actSize, rawData(), rawData() + actSize);
220     }
221 
222     operator ArrayView<T>() {
223         return ArrayView<T>(rawData(), actSize);
224     }
225 
226     operator ArrayView<const T>() const {
227         return ArrayView<const T>(rawData(), actSize);
228     }
229 
230     bool operator==(const StaticArray& other) const {
231         if (actSize != other.actSize) {
232             return false;
233         }
234         for (TCounter i = 0; i < actSize; ++i) {
235             if (data[i].get() != other.data[i].get()) {
236                 return false;
237             }
238         }
239         return true;
240     }
241 
242     bool operator!=(const StaticArray& other) const {
243         return !(*this == other);
244     }
245 
246     /// \brief Prints content of array to stream.
247     ///
248     /// Stored values must have overloaded << operator.
249     template <typename TStream>
250     friend TStream& operator<<(TStream& stream, const StaticArray& array) {
251         for (const T& t : array) {
252             stream << t << std::endl;
253         }
254         return stream;
255     }
256 
257 private:
rawData()258     INLINE StorageType* rawData() {
259         return reinterpret_cast<StorageType*>(data);
260     }
261 
rawData()262     INLINE const StorageType* rawData() const {
263         return reinterpret_cast<const StorageType*>(data);
264     }
265 };
266 
267 /// \brief Creates a static array from a list of parameters.
268 ///
269 /// All parameters must have the same type. Both the allocated size of the array and the number of constructed
270 /// elements equal to the number of parameters.
271 template <typename T0, typename... TArgs>
makeStatic(T0 && t0,TArgs &&...rest)272 StaticArray<T0, sizeof...(TArgs) + 1> makeStatic(T0&& t0, TArgs&&... rest) {
273     return StaticArray<T0, sizeof...(TArgs) + 1>({ std::forward<T0>(t0), std::forward<TArgs>(rest)... });
274 }
275 
276 /// \brief Creates a static array from a list of l-value references.
277 ///
278 /// All parameters must have the same type. Both the allocated size of the array and the number of constructed
279 /// elements equal to the number of parameters.
280 template <typename T0, typename... TArgs>
tie(T0 & t0,TArgs &...rest)281 StaticArray<T0&, sizeof...(TArgs) + 1> tie(T0& t0, TArgs&... rest) {
282     return StaticArray<T0&, sizeof...(TArgs) + 1>({ t0, rest... });
283 }
284 
285 /// \brief Alias for array holding two elements of the same type.
286 template <typename T>
287 using Pair = StaticArray<T, 2>;
288 
289 
290 /// \brief Container similar to \ref StaticArray, but with constexpr constructors and getters.
291 template <typename T, Size N>
292 class ConstexprArray {
293 private:
294     T data[N];
295 
296 public:
297     template <typename... TArgs>
ConstexprArray(TArgs &&...args)298     constexpr ConstexprArray(TArgs&&... args)
299         : data{ std::forward<TArgs>(args)... } {}
300 
301     constexpr const T& operator[](const Size idx) const {
302         return data[idx];
303     }
304 
305     constexpr T& operator[](const Size idx) {
306         return data[idx];
307     }
308 };
309 
310 NAMESPACE_SPH_END
311