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