SuperDex Physics C++ API
Loading...
Searching...
No Matches
dynamic_array.h
Go to the documentation of this file.
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#pragma once
18
22
23#if MOCHI_LANGUAGE_CPP20
24#include <concepts>
25#endif
26#include <algorithm>
27#include <cstddef>
28#include <iterator>
29#include <limits>
30#include <tuple>
31#include <type_traits>
32#include <utility>
33
34namespace superdex {
35
36// Define MOCHI_DARRAY_DEBUG to 1 to enable safety checks in DynamicArray
37#ifndef MOCHI_DARRAY_DEBUG
38#define MOCHI_DARRAY_DEBUG MOCHI_DEBUG
39#endif
40
41#if MOCHI_DARRAY_DEBUG && MOCHI_ASSERT_ENABLED
42#define MOCHI_DARRAY_ASSERT(cond, ...) MOCHI_ASSERT(cond, __VA_ARGS__)
43#else
44#define MOCHI_DARRAY_ASSERT(cond, ...)
45#endif
46
47#if MOCHI_LANGUAGE_CPP20
48// Only until unrealios-android-mochiunreal gets an updated compiler that supports the
49// std::forward_iterator concept.
50template <class From, class To>
52 std::is_convertible_v<From, To> && requires { static_cast<To>(std::declval<From>()); };
53template <typename T>
54concept HasDistance = requires(T t) {
55 { std::distance(t, t) } -> ConvertibleTo<std::ptrdiff_t>;
56};
57#define MOCHI_DARRAY_ASSERT_FORWARD_ITERATOR(IteratorType) \
58 static_assert( \
59 HasDistance<IteratorType>, \
60 "This type of input iterator is not supported by DynamicArray because there is no way to " \
61 "reserve sufficient memory up front. We would have to loop calling emplace_back, which " \
62 "would be inefficient. Consider initializing this DynamicArray in a different way.");
63#else
64#define MOCHI_DARRAY_ASSERT_FORWARD_ITERATOR(IteratorType) \
65 static_assert( \
66 std::is_base_of_v< \
67 std::forward_iterator_tag, \
68 typename std::iterator_traits<IteratorType>::iterator_category>, \
69 "This type of input iterator is not supported by DynamicArray because there is no way to " \
70 "reserve sufficient memory up front. We would have to loop calling emplace_back, which " \
71 "would be inefficient. Consider initializing this DynamicArray in a different way.");
72#endif
73
74/** @brief Trait to determine if a type is safe for use with @ref DynamicArray::resize_noinit. */
75template <typename T>
76struct IsResizeNoInitSafe : std::bool_constant<std::is_trivially_copyable_v<T>> {};
77
78/** @brief Helper variable template for @ref IsResizeNoInitSafe. */
79template <typename T>
81
82/**
83 * @brief A dynamically resizable array with syntax and behavior similar to std::pmr::vector
84 *
85 * @remarks
86 * - Optionally provide your own polymorphic allocator.
87 * - Compatible with FILO allocators as long as you reserve or resize sufficient memory.
88 * - Extends the std::pmr::vector syntax with new methods including resize_noinit and append.
89 *
90 * @warning DynamicArray does not provide equivalent exception safety guarantees as std::vector.
91 * Exception-safe use assumes that element construction, assignment, destruction, move operations,
92 * and allocator operations do not throw. For example:
93 * - Reallocation via emplace_back, push_back, append, assign, reserve, resize, or shrink_to_fit
94 * can leak memory or leave the array in an invalid state if T's move constructor throws.
95 * - Construction, append, assign, and resize can leak memory or leave the array in an invalid
96 * state if T's constructors or assignment operators throw.
97 * - Moving from a DynamicArray with a different allocator does not provide the same rollback or
98 * cleanup guarantees as std::vector if moving elements throws.
99 * - Allocator exceptions may leave the array in an invalid state in code paths that release old
100 * storage before allocating new storage.
101 *
102 * @tparam T Element value type.
103 */
104template <class T>
106 public:
107 static_assert(
108 !std::is_same_v<T, Allocator> && !std::is_same_v<T, Allocator*>,
109 "DynamicArray of allocators is not allowed because of overload ambiguity.");
110
111 using const_iterator = T const*;
112 using iterator = T*;
113 using value_type = T;
114 using size_type = size_t;
115
116 /**
117 * @brief Construct an empty DynamicArray with the default allocator.
118 */
119 DynamicArray() = default;
120
121 /**
122 * @brief Construct an empty DynamicArray with a specific allocator.
123 *
124 * @param allocator Pointer to a polymorphic allocator. Must outlive this DynamicArray object.
125 */
126 explicit DynamicArray(Allocator* allocator) : _allocator(allocator) {}
127
128 /**
129 * @brief Construct a DynamicArray with an initial size. All elements will be default constructed
130 * or zero-initialized (for POD types).
131 *
132 * @param size Initial size (number of elements, not bytes)
133 * @param allocator Pointer to a polymorphic allocator. Must outlive this DynamicArray object.
134 */
136 : _begin(static_cast<T*>(allocator->allocate(size * sizeof(T), alignof(T)))),
137 _end(_begin + size),
138 _endCapacity(_end),
139 _allocator(allocator) {
140 DefaultConstructRange(_begin, _end);
141 }
142
143 /**
144 * @brief Construct a DynamicArray filled with copies of the specified value.
145 *
146 * @tparam FromT Input value type. Must be same or convertible to type T.
147 * @param size Initial size (number of elements, not bytes)
148 * @param defaultValue Value to copy to all new array elements.
149 * @param allocator Pointer to a polymorphic allocator. Must outlive this DynamicArray object.
150 */
151 template <typename FromT, MOCHI_CONCEPT((std::is_convertible_v<FromT, T>))>
154 FromT const& defaultValue,
155 Allocator* allocator = GetDefaultAllocator())
156 : _begin(static_cast<T*>(allocator->allocate(size * sizeof(T), alignof(T)))),
157 _end(_begin + size),
158 _endCapacity(_end),
159 _allocator(allocator) {
160 CopyConstructRangeFromValue(_begin, _end, defaultValue);
161 }
162
163 /**
164 * @brief Construct a DynamicArray and copy values from the half-open range:
165 * [rangeBegin, rangeEnd)
166 *
167 * @tparam InputIt Input forward iterator type (typically deduced).
168 * @param rangeBegin Iterator pointing to the first input value to copy.
169 * @param rangeEnd Iterator pointing ONE PAST the last input value to copy.
170 * @param allocator Pointer to a polymorphic allocator. Must outlive this DynamicArray object.
171 */
172 template <
173 typename InputIt,
174 MOCHI_CONCEPT((std::is_base_of_v<
175 std::input_iterator_tag,
176 typename std::iterator_traits<InputIt>::iterator_category>))>
177 DynamicArray(InputIt rangeBegin, InputIt rangeEnd, Allocator* allocator = GetDefaultAllocator())
178 : _allocator(allocator) {
180 auto size = static_cast<size_type>(std::distance(rangeBegin, rangeEnd));
181 _begin = static_cast<T*>(allocator->allocate(size * sizeof(T), alignof(T)));
182 _end = _endCapacity = _begin + size;
183 CopyConstructRange(_begin, rangeBegin, rangeEnd);
184 }
185
186 /**
187 * @brief Construct a DynamicArray and copy values from a std::initializer_list.
188 *
189 * @param list List from which values will be copied.
190 * @param allocator Pointer to a polymorphic allocator. Must outlive this DynamicArray object.
191 */
192 DynamicArray(std::initializer_list<T> const& list, Allocator* allocator = GetDefaultAllocator())
193 : DynamicArray(std::begin(list), std::end(list), allocator) {}
194
195 /**
196 * @brief Construct a DynamicArray and copy values from another iterable container type like
197 * superdex::Span, std::vector, std::list, etc...
198 *
199 * @tparam InputContainerT Input container type. Must support std::begin and std::end.
200 * @param other Container from which values will be copied.
201 * @param allocator Pointer to a polymorphic allocator. Must outlive this DynamicArray object.
202 */
203 template <
204 typename InputContainerT,
206 (!std::is_same_v<std::decay_t<InputContainerT>, DynamicArray> &&
207 sizeof(decltype(std::begin(std::declval<InputContainerT const&>()))) &&
208 sizeof(decltype(std::end(std::declval<InputContainerT const&>())))))>
209 explicit DynamicArray(InputContainerT const& other, Allocator* allocator = GetDefaultAllocator())
210 : DynamicArray(std::begin(other), std::end(other), allocator) {}
211
212 /**
213 * @brief Construct a DynamicArray and copy values from another one.
214 *
215 * @param other Another DynamicArray from which values will be copied.
216 * @param allocator Pointer to a polymorphic allocator. Must outlive this DynamicArray object.
217 */
218 DynamicArray(DynamicArray const& other, Allocator* allocator)
219 : DynamicArray(other.begin(), other.end(), allocator) {}
220
221 /**
222 * @brief Copy construct from another DynamicArray. Use the same allocator.
223 *
224 * @param other Another DynamicArray from which the allocator and the values will be copied.
225 */
227 : DynamicArray(other.begin(), other.end(), other._allocator) {}
228
229 /**
230 * @brief Construct a DynamicArray by moving memory or values from another one.
231 *
232 * @remarks If the allocators are equal, then array memory ownership will be transferred from
233 * the other array to this one. Otherwise, new memory will be allocated for this array and then
234 * the other array's values will be moved into it. Either way, the other array will be empty
235 * after this call.
236 *
237 * @param other Another DynamicArray from which memory or values will be moved
238 * @param allocator Pointer to a polymorphic allocator. Must outlive this DynamicArray object.
239 */
240 DynamicArray(DynamicArray&& other, Allocator* allocator) : _allocator(allocator) {
241 if (other._allocator->is_equal(*_allocator))
243 _begin = other._begin;
244 _end = other._end;
245 _endCapacity = other._endCapacity;
246 other._begin = other._end = other._endCapacity = nullptr;
247 }
248 else {
249 auto size = other.size();
250 _begin = static_cast<T*>(_allocator->allocate(size * sizeof(T), alignof(T)));
251 _end = _begin;
252 _endCapacity = _begin + size;
253 // Move construct each element individually, advancing _end after each successful
254 // construction.
255 for (auto src = other._begin; src < other._end; ++src, ++_end) {
256 new (_end) T(std::move(*src)); // Move construct
257 }
258 other.clear();
259 }
260 }
261
262 /**
263 * @brief Move construct from another DynamicArray. Use the same allocator.
264 * @remarks Memory ownership is always transferred. No new allocation.
265 *
266 * @param other Another DynamicArray from which memory will be moved. Will be empty after this
267 * call.
268 */
269 DynamicArray(DynamicArray&& other) noexcept
270 : _begin(other._begin),
271 _end(other._end),
272 _endCapacity(other._endCapacity),
273 _allocator(other._allocator) {
274 other._begin = other._end = other._endCapacity = nullptr;
275 }
276
277 /**
278 * @brief Destroy the DynamicArray object and all of its elements. Deallocate any memory that
279 * was used.
280 */
282 if (_begin != nullptr) {
283 DestroyRange(_begin, _end);
284 _allocator->deallocate(_begin, capacity() * sizeof(T), alignof(T));
285#if MOCHI_DARRAY_DEBUG
286 _begin = _end = _endCapacity = nullptr;
287 _allocator = nullptr;
288#endif // MOCHI_DARRAY_DEBUG
289 }
290 }
291
292 /**
293 * @brief Get an iterator pointing to the beginning of the array.
294 */
296 return _begin;
297 }
298
299 /**
300 * @brief Get a const iterator pointing to the beginning of the array.
301 */
303 return _begin;
304 }
305
306 /**
307 * @brief Get a const iterator pointing to the beginning of the array.
308 */
310 return _begin;
311 }
312
313 /**
314 * @brief Get an iterator pointing to the next element AFTER the end of the array.
315 */
317 return _end;
318 }
319
320 /**
321 * @brief Get a const iterator pointing to the next element AFTER the end of the array.
322 */
323 [[nodiscard]] MOCHI_FORCE_INLINE const_iterator end() const {
324 return _end;
325 }
326
327 /**
328 * @brief Get a const iterator pointing to the next element AFTER the end of the array.
329 */
330 [[nodiscard]] MOCHI_FORCE_INLINE const_iterator cend() const {
331 return _end;
332 }
333
334 /**
335 * @brief Get a reference to the first element. Array must not be empty.
336 */
337 [[nodiscard]] MOCHI_FORCE_INLINE T& front() {
338 MOCHI_DARRAY_ASSERT(!empty(), "Cannot get front of empty array");
339 return *_begin;
340 }
341
342 /**
343 * @brief Get a const reference to the first element. Array must not be empty.
344 */
345 [[nodiscard]] MOCHI_FORCE_INLINE T const& front() const {
346 MOCHI_DARRAY_ASSERT(!empty(), "Cannot get front of empty array");
347 return *_begin;
348 }
349
350 /**
351 * @brief Get a reference to the last element. Array must not be empty.
352 */
353 [[nodiscard]] MOCHI_FORCE_INLINE T& back() {
354 MOCHI_DARRAY_ASSERT(!empty(), "Cannot get back of empty array");
355 return *(_end - 1);
356 }
357
358 /**
359 * @brief Get a const reference to the last element. Array must not be empty.
360 */
361 [[nodiscard]] MOCHI_FORCE_INLINE T const& back() const {
362 MOCHI_DARRAY_ASSERT(!empty(), "Cannot get back of empty array");
363 return *(_end - 1);
364 }
365
366 /**
367 * @brief Get a pointer to the first element. May be nullptr if this array is empty.
368 */
369 [[nodiscard]] MOCHI_FORCE_INLINE T* data() {
370 return _begin;
371 }
372
373 /**
374 * @brief Get a const pointer to the first element. May be nullptr if this array is empty.
375 */
376 [[nodiscard]] MOCHI_FORCE_INLINE T const* data() const {
377 return _begin;
378 }
379
380 /**
381 * @brief Get the number of elements in the array.
382 */
383 [[nodiscard]] MOCHI_FORCE_INLINE size_type size() const {
384 return _end - _begin;
385 }
386
387 /**
388 * @brief Get the number of elements that could fit in the array without allocating more memory.
389 */
390 [[nodiscard]] MOCHI_FORCE_INLINE size_type capacity() const {
391 return _endCapacity - _begin;
392 }
393
394 /**
395 * @brief Return true if the array is empty (zero size).
396 */
397 [[nodiscard]] MOCHI_FORCE_INLINE bool empty() const {
398 return _end == _begin;
399 }
400
401 /**
402 * @brief Set the size. If larger than the current size, new elements will be default constructed
403 * (zero-initialized for POD types).
404 *
405 * @param newSize New number of elements
406 *
407 * @remarks The first time you call resize, capacity will be allocated for the exact size
408 * requested. However, subsequent calls to resize may allocate more than requested, similar to
409 * emplace_back.
410 */
411 void resize(size_type newSize) {
412 if (newSize > size()) {
413 if (newSize > capacity()) {
414 GrowCapacity(_begin ? GetNextCapacity(newSize) : newSize);
415 }
416 DefaultConstructRange(_end, _begin + newSize);
417 } else {
418 DestroyRange(_begin + newSize, _end);
419 }
420 _end = _begin + newSize;
421 }
422
423 /**
424 * @brief Set the size. If larger than the current size, new elements will be copy constructed
425 * from the given value.
426 *
427 * @param newSize New number of elements
428 * @param value Value to copy to new elements (if any).
429 *
430 * @warning The value parameter is not allowed to be a reference to an element within this
431 * same DynamicArray. In that case, consider using a copy of the value instead.
432 *
433 * @remarks The first time you call resize, capacity will be allocated for the exact size
434 * requested. However, subsequent calls to resize may allocate more than requested, similar to
435 * emplace_back.
436 */
437 void resize(size_type newSize, T const& value) {
438 if (newSize > size()) {
439 if (newSize > capacity()) {
440 GrowCapacity(_begin ? GetNextCapacity(newSize) : newSize);
441 }
442 CopyConstructRangeFromValue(_end, _begin + newSize, value);
443 } else {
444 DestroyRange(_begin + newSize, _end);
445 }
446 _end = _begin + newSize;
447 }
448
449 /**
450 * @brief Set the size but do not initialize any new elements. They will be in an undefined state!
451 *
452 * @param newSize New number of elements
453 *
454 * @warning This method may be used as an optimization, but only if you can guarantee that any new
455 * elements will be initialized before being used.
456 *
457 * @note Only supported for types satisfying @ref kIsResizeNoInitSafe.
458 * @note The first time you call resize_noinit, capacity will be allocated for the exact size
459 * requested. However, subsequent calls to resize_noinit may allocate more than requested, similar
460 * to emplace_back.
461 */
462 void resize_noinit(size_type newSize) {
463 static_assert(
464 kIsResizeNoInitSafe<T>, "DynamicArray::resize_noinit is not supported for this type");
465 size_type prevSize = size();
466 if (newSize > prevSize) {
467 if (newSize > capacity()) {
468 GrowCapacity(_begin ? GetNextCapacity(newSize) : newSize);
469 }
470 _end = _begin + newSize;
471 DebugFillWithNaN(_begin + prevSize, _end);
472 } else {
473 DestroyRange(_begin + newSize, _end);
474 _end = _begin + newSize;
475 }
476 }
477
478 /**
479 * @brief Reset the state of this DynamicArray using the arguments from any constructor. Any
480 * previous elements will be destroyed. Any previous memory will be deallocated.
481 *
482 * @remarks This method can be used to assign a new allocator pointer to a DynamicArray object
483 * that has already been constructed (unlike move assignment, which does not move the allocator).
484 *
485 * @tparam Args Any argument types supported by one of the DynamicArray constructors.
486 * @param args Any parameter types supported by one of the DynamicArray constructors.
487 */
488 template <typename... Args>
489 void reset(Args&&... args) {
490 this->~DynamicArray<T>();
491 new (this) DynamicArray<T>(std::forward<Args>(args)...);
492 }
493
494 /**
495 * @brief Ensure that this array has at least the specified capacity.
496 *
497 * @param newCapacity Desired capacity measured by number of elements (not bytes).
498 *
499 * @warning If you call reserve repeatedly with incrementally larger values, it will reallocate
500 * the memory every time. It will not overallocate the memory the way emplace_back does.
501 */
502 void reserve(size_type newCapacity) {
503 if (newCapacity > capacity()) {
504 GrowCapacity(newCapacity);
505 }
506 }
507
508 /**
509 * @brief Set the size to zero. Does not deallocate memory.
510 * @see shrink_to_fit
511 */
512 void clear() {
513 DestroyRange(_begin, _end);
514 _end = _begin;
515 }
516
517 /**
518 * @brief Ensure that the capacity (memory allocated) is no larger than the size (memory used).
519 */
521 if (_endCapacity > _end) {
522 if (empty()) {
523 _allocator->deallocate(_begin, capacity() * sizeof(T), alignof(T));
524 _begin = _end = _endCapacity = nullptr;
525 } else {
526 auto const n = size();
527 auto* newBegin = static_cast<T*>(_allocator->allocate(n * sizeof(T), alignof(T)));
528 MoveConstructRange(newBegin, _begin, n);
529 DestroyRange(_begin, _end);
530 _allocator->deallocate(_begin, capacity() * sizeof(T), alignof(T));
531 _begin = newBegin;
532 _end = _endCapacity = newBegin + n;
533 }
534 }
535 }
536
537 /**
538 * @brief Emplace a new element at the end of the array.
539 *
540 * @remarks If people use reserve or resize, then we will allocate the exact capacity requested.
541 * However, if people call push_back or emplace_back repeatedly, we will grow the capacity by 50%
542 * each time more memory is needed. This achieves amortized O(1) cost, similar to std::vector.
543 *
544 * Unlike std::vector, we skip past the first few reallocations by reserving a capacity of
545 * kGrowthPatternStartSize the first time. See GetNextCapacity() for details.
546 *
547 * @warning The argument is not allowed to be a reference to an element within this same
548 * DynamicArray. In that case, consider emplacing a copy of the element instead.
549 *
550 * @tparam Args Any types that can be passed to type T's constructor.
551 * @param args Any arguments that can be passed to type T's constructor.
552 */
553 template <class... Args>
554 void emplace_back(Args&&... args) {
555 if constexpr (sizeof...(Args) == 1) {
556 if constexpr (std::is_same_v<
557 T,
558 std::remove_cv_t<std::remove_reference_t<decltype(std::get<0>(
559 std::forward_as_tuple(args...)))>>>) {
561 !IsInThisArray(&std::get<0>(std::forward_as_tuple(args...))),
562 "You are not allowed to push an element of a DynamicArray onto the end of the same DynamicArray. "
563 "Consider pushing a copy of the value instead.");
564 }
565 }
566 if (_endCapacity == _end)
568 GrowCapacity(GetNextCapacity(size() + 1));
569 }
570 new (_end) T(std::forward<Args>(args)...);
571 ++_end;
572 }
573
574 /**
575 * @brief Copy the value to a new element at the end of the array.
576 *
577 * @remarks May over allocate to achieve amortized O(1) cost like emplace_back.
578 *
579 * @warning The argument is not allowed to be a reference to an element within this same
580 * DynamicArray. In that case, consider pushing a copy of the element instead.
581 *
582 * @param value Value to be copied
583 * @see emplace_back
584 */
585 MOCHI_FORCE_INLINE void push_back(T const& value) {
586 emplace_back(value); // emplace via copy constructor
587 }
588
589 /**
590 * @brief Move the value to a new element at the end of the array.
591 *
592 * @remarks May over allocate to achieve amortized O(1) cost like emplace_back.
593 *
594 * @warning The argument is not allowed to be a reference to an element within this same
595 * DynamicArray. In that case, consider pushing a copy of the element instead.
596 *
597 * @param value Value to be moved
598 * @see emplace_back
599 */
600 void push_back(T&& value) {
601 emplace_back(std::move(value)); // emplace via move constructor
602 }
603
604 /**
605 * @brief Default construct a new element at the end of the array.
606 * @remarks May over allocate to achieve amortized O(1) cost like emplace_back.
607 *
608 * @code{.cpp}
609 * auto& obj = myArray.push_back();
610 * obj.value = someValue;
611 * @endcode
612 *
613 * @return T& A reference to the new element
614 */
616 emplace_back();
617 return back();
618 }
619
620 /**
621 * @brief Remove the last element of the array (must not be empty).
622 */
623 void pop_back() {
624 MOCHI_DARRAY_ASSERT(!empty(), "Cannot pop_back an empty array");
625 --_end;
626 if constexpr (!std::is_trivially_destructible_v<T>) {
627 _end->~T();
628 }
629 }
630
631 /**
632 * @brief Remove the half-open range [rangeBegin, rangeEnd) from this array. Any later elements
633 * will be shifted down to preserve order.
634 *
635 * @remarks Size will be reduced by (rangeEnd - rangeBegin).
636 *
637 * @param rangeBegin_ Iterator pointing to the first element to remove.
638 * @param rangeEnd_ Iterator pointing ONE PAST the last element to remove.
639 */
640 void erase(const_iterator rangeBegin_, const_iterator rangeEnd_) {
641 // Const cast so we don't have to implement both erase(iterator, iterator) and
642 // erase(const_iterator, const_iterator).
643 auto rangeBegin = const_cast<iterator>(rangeBegin_);
644 auto rangeEnd = const_cast<iterator>(rangeEnd_);
645 if (rangeEnd == _end) {
646 MOCHI_DARRAY_ASSERT(rangeBegin >= _begin && rangeBegin <= _end, "Invalid iterator");
647 // Erase from the end. This is the most common case.
648 DestroyRange(rangeBegin, rangeEnd);
649 _end = rangeBegin;
650 } else if (rangeEnd != rangeBegin) {
651 MOCHI_DARRAY_ASSERT(rangeBegin <= rangeEnd, "Invalid iterator range");
652 MOCHI_DARRAY_ASSERT(IsInThisArray(rangeBegin, rangeEnd), "Invalid iterator");
653 // Erase from the middle and shift elements down
654 if constexpr (std::is_trivially_move_assignable_v<T>) {
655 memmove(rangeBegin, rangeEnd, (_end - rangeEnd) * sizeof(T));
656 } else {
657 T* dst = rangeBegin;
658 T* src = const_cast<T*>(rangeEnd); // First value to move, after the range to erase
659 for (; src != _end; ++dst, ++src) {
660 *dst = std::move(*src); // Move assignment
661 }
662 }
663 auto numRemoved = (rangeEnd - rangeBegin);
664 auto newSize = size() - numRemoved;
665 DestroyRange(_begin + newSize, _end);
666 _end = _begin + newSize;
667 }
668 }
669
670 /**
671 * @brief Remove one element from this array. Any later elements will be shifted down to preserve
672 * order.
673 *
674 * @param it_ Iterator pointing to the element to remove.
675 */
677 // Const cast so we don't have to implement both erase(iterator) and erase(const_iterator).
678 auto it = const_cast<iterator>(it_);
679 MOCHI_DARRAY_ASSERT(it >= _begin && it < _end, "Invalid iterator");
680 if (it + 1 == _end) {
681 // Erase from the end. This is the most common case.
682 Destroy(it);
683 _end = it;
684 } else {
685 erase(it, it + 1);
686 }
687 }
688
689 /**
690 * @brief Remove one element from this array and swap the last element into its place. This
691 * achieves O(1) cost but does not preserve order.
692 *
693 * @param it Iterator pointing to the element to remove.
694 */
696 MOCHI_DARRAY_ASSERT(it >= _begin && it < _end, "Invalid iterator");
697 auto* last = _end - 1;
698 if (it != last) {
699 *it = std::move(*last);
700 }
701 Destroy(last);
702 _end = last;
703 }
704
705 /**
706 * @brief Replace the contents of this DynamicArray by copying values from the half-open range
707 * [rangeBegin, rangeEnd).
708 *
709 * @warning The iterators are not allowed to point to elements within this same DynamicArray.
710 *
711 * @tparam InputIt Input iterator type (typically deduced).
712 * @param rangeBegin Iterator pointing to the first value to copy from.
713 * @param rangeEnd Iterator pointing ONE PAST the last value to copy from.
714 */
715 template <
716 typename InputIt,
717 MOCHI_CONCEPT((std::is_base_of_v<
718 std::input_iterator_tag,
719 typename std::iterator_traits<InputIt>::iterator_category>))>
720 void assign(InputIt rangeBegin, InputIt rangeEnd) {
722 if constexpr (std::is_pointer_v<InputIt>) {
723 MOCHI_DARRAY_ASSERT(rangeBegin <= rangeEnd, "Invalid input range");
725 !IsInThisArray(rangeBegin, rangeEnd),
726 "The input range is not allowed to point to memory within the DynamicArray that is being modified.");
727 }
728 auto const oldSize = size();
729 auto const newSize = static_cast<size_type>(std::distance(rangeBegin, rangeEnd));
731 _allocator || (newSize == oldSize),
732 "It is illegal to change the size of a DynamicArray that does not own the memory.");
733 if (newSize <= oldSize) {
734 // This array is getting smaller
735 _end = _begin + newSize;
736 DestroyRange(_begin + newSize, _begin + oldSize);
737 std::copy(rangeBegin, rangeEnd, _begin);
738 } else if (newSize <= capacity()) {
739 // This array is getting larger. Sufficient capacity exists.
740 _end += (newSize - oldSize);
741 if constexpr (std::is_trivially_copyable_v<T>) {
742 // We can copy the full range without worrying about a user-defined constructor or
743 // assignment operator.
744 std::copy(rangeBegin, rangeEnd, _begin);
745 } else {
746 // Copy assign existing values
747 auto src = rangeBegin;
748 std::copy_n(src, oldSize, _begin);
749 // Copy construct new values
750 std::advance(src, oldSize);
751 for (size_type i = oldSize; i < newSize; ++i, ++src) {
752 new (_begin + i) T(*src);
753 }
754 }
755 } else {
756 // This array is getting larger and needs to allocate new capacity
757 if constexpr (
758 std::is_trivially_copyable_v<T> && std::is_trivially_move_constructible_v<T> &&
759 std::is_trivially_move_assignable_v<T> && std::is_trivially_destructible_v<T>) {
760 // We can deallocate the old memory before allocating the new memory because we don't need
761 // to read nor destruct the old values. We can simply copy the full range to the new
762 // memory location.
763 if (_begin) {
764 _allocator->deallocate(_begin, capacity() * sizeof(T), alignof(T));
765 }
766 _begin = static_cast<T*>(_allocator->allocate(newSize * sizeof(T), alignof(T)));
767 _end = _endCapacity = _begin + newSize;
768 std::copy(rangeBegin, rangeEnd, _begin);
769 } else {
770 // Allocate memory and move existing values.
771 GrowCapacity(newSize); // exact capacity
772 _end = _begin + newSize;
773 // Copy assign over existing values
774 auto src = rangeBegin;
775 std::copy_n(src, oldSize, _begin);
776 // Copy construct new values
777 std::advance(src, oldSize);
778 CopyConstructRange(_begin + oldSize, src, rangeEnd);
779 }
780 }
781 }
782
783 /**
784 * @brief Add values to the end of this DynamicArray by copying them from the half-open range
785 * [rangeBegin, rangeEnd).
786 *
787 * @warning The iterators are not allowed to point to elements within this same DynamicArray.
788 * Consider appending a copy of the elements instead.
789 *
790 * @tparam InputIt Input iterator type (typically deduced).
791 * @param rangeBegin Iterator pointing to the first input value to copy.
792 * @param rangeEnd Iterator pointing ONE PAST the last input value to copy.
793 */
794 template <class InputIt>
795 void append(InputIt rangeBegin, InputIt rangeEnd) {
798 !IsInThisArray(rangeBegin, rangeEnd),
799 "The input range is not allowed to point to memory within the DynamicArray that is being modified.");
800 auto count = std::distance(rangeBegin, rangeEnd);
801 MOCHI_DARRAY_ASSERT(count >= 0, "Invalid input range");
802 auto prevSize = size();
803 auto newSize = prevSize + count;
804 if (newSize > capacity())
806 GrowCapacity(GetNextCapacity(newSize));
807 }
808 _end = _begin + newSize;
809 CopyConstructRange(_begin + prevSize, rangeBegin, rangeEnd);
810 }
811
812 /**
813 * @brief Add values to the end of this DynamicArray by copying them from another iterable
814 * container.
815 *
816 * @warning It is illegal to append a DynamicArray to itself.
817 *
818 * @tparam InputContainerT Another iterable container type. Must support std::begin and
819 * std::end.
820 * @param container Container from which value will be copied.
821 */
822 template <class InputContainerT>
823 MOCHI_FORCE_INLINE void append(InputContainerT const& container) {
824 append(std::begin(container), std::end(container));
825 }
826
827 /**
828 * @brief Get a pointer to the polymorphic allocator.
829 */
831 return _allocator;
832 }
833
834 /**
835 * @brief Get a reference to the value at index i.
836 */
838 MOCHI_DARRAY_ASSERT(i < size(), "Index out of range");
839 return _begin[i];
840 }
841
842 /**
843 * @brief Get a reference to the value at index i.
844 */
845 [[nodiscard]] MOCHI_FORCE_INLINE T const& operator[](size_type i) const {
846 MOCHI_DARRAY_ASSERT(i < size(), "Index out of range");
847 return _begin[i];
848 }
849
850 /**
851 * @brief Copy Assignment: Replace the contents of this DynamicArray by copying values from
852 * another one.
853 *
854 * @param other Another DynamicArray from which values will be copied.
855 * @return *this
856 */
858 if (&other != this)
860 assign(other.begin(), other.end());
861 }
862 return *this;
863 }
864
865 /**
866 * @brief Move Assignment: Replace the contents of this DynamicArray by moving memory or values
867 * from another one.
868 *
869 * @remarks If the allocators are equal, then array memory ownership will be transferred from
870 * the other array to this one. Otherwise, new memory will be allocated for this array (if
871 * necessary) and then the other array's values will be moved into it. Either way, the other
872 * array will be empty after this call.
873 *
874 * @param other Another DynamicArray from which memory or values will be moved.
875 * @return *this
876 */
878 if (&other != this)
880 clear();
881 if (_allocator->is_equal(*other._allocator))
883 // Release any previously allocated memory
884 if (_begin != nullptr) {
885 _allocator->deallocate(_begin, capacity() * sizeof(T), alignof(T));
886 }
887 // Move ownership of the other memory to this array
888 _begin = other._begin;
889 _end = other._end;
890 _endCapacity = other._endCapacity;
891 other._begin = other._end = other._endCapacity = nullptr;
892 }
893 else if (!other.empty()) {
894 // Ensure sufficient capacity using our allocator
895 auto const newSize = other.size();
896 if (newSize > capacity()) {
897 GrowCapacity(newSize);
898 }
899 // Move construct each element individually, advancing _end after each successful
900 // construction. If T's move constructor throws, the destructor will only destroy elements
901 // that were successfully constructed.
902 for (auto src = other._begin; src < other._end; ++src, ++_end) {
903 new (_end) T(std::move(*src)); // Move construct
904 }
905 // Destroy the elements in the rhs array, which have already been moved.
906 // No need to release memory in the rhs array at this time.
907 other.clear();
908 }
909 }
910 return *this;
911 }
912
913 /**
914 * @brief Copy Assignment: Replace the contents of this DynamicArray by copying values from
915 * a std::initializer_list.
916 *
917 * @param list List of values to copy
918 * @return *this
919 */
920 DynamicArray& operator=(std::initializer_list<T> const& list) {
921 assign(std::begin(list), std::end(list));
922 return *this;
923 }
924
925 /**
926 * @brief Copy Assignment: Replace the contents of this DynamicArray by copying values from
927 * another iterable container of compatible type.
928 *
929 * @tparam InputContainerT Another container type. Must support std::begin and std::end.
930 * @param other Container from which values will be copied.
931 * @return *this
932 */
933 template <
934 typename InputContainerT,
936 (!std::is_same_v<std::decay_t<InputContainerT>, DynamicArray> &&
937 sizeof(decltype(std::begin(std::declval<InputContainerT const&>()))) &&
938 sizeof(decltype(std::end(std::declval<InputContainerT const&>())))))>
939 DynamicArray& operator=(InputContainerT const& other) {
940 assign(std::begin(other), std::end(other));
941 return *this;
942 }
943
944 /**
945 * @brief Comparison returns true if the other array has the same size and all values are equal.
946 */
947 bool operator==(DynamicArray const& other) const {
948 auto s = size();
949 if (other.size() == s) {
950 for (size_type i = 0; i < s; ++i) {
951 if (other._begin[i] != _begin[i]) {
952 return false;
953 }
954 }
955 return true;
956 } else {
957 return false;
958 }
959 }
960
961 /**
962 * @brief Comparison returns false unless the other array has the same size and all values are
963 * equal.
964 */
965 MOCHI_FORCE_INLINE bool operator!=(DynamicArray const& other) const {
966 return !(*this == other);
967 }
968
969 private:
970 void GrowCapacity(size_type newCapacity) {
971 MOCHI_DARRAY_ASSERT(newCapacity > capacity(), "This method only increases capacity");
972 if (empty()) {
973 if (_begin) {
974 // Free previous allocation first, in case of a FILO allocator.
975 _allocator->deallocate(_begin, capacity() * sizeof(T), alignof(T));
976 }
977 _begin = _end = static_cast<T*>(_allocator->allocate(newCapacity * sizeof(T), alignof(T)));
978 _endCapacity = _begin + newCapacity;
979 } else {
980 auto oldSize = size();
981 auto* newBegin = static_cast<T*>(_allocator->allocate(newCapacity * sizeof(T), alignof(T)));
982 for (auto i = static_cast<ptrdiff_t>(oldSize) - 1; i >= 0; --i) {
983 new (&newBegin[i]) T(std::move(_begin[i])); // Move via placement new
984 if constexpr (!std::is_trivially_destructible_v<T>) {
985 _begin[i].~T(); // Destroy
986 }
987 }
988 _allocator->deallocate(_begin, capacity() * sizeof(T), alignof(T));
989 _begin = newBegin;
990 _end = newBegin + oldSize;
991 _endCapacity = newBegin + newCapacity;
992 }
993 }
994
995 size_type GetNextCapacity(size_type minCapacity) const {
996 // Jump to kGrowthPatternStartSize, then grow by 50% each time after that.
997 return std::max(std::max(minCapacity, kGrowthPatternStartSize), capacity() * 3 / 2);
998 }
999
1000 static void DebugFillWithNaN(
1001 [[maybe_unused]] void* rangeBegin,
1002 [[maybe_unused]] void const* rangeEnd) {
1003#if MOCHI_DARRAY_DEBUG
1004 // Fill as much of the range as we can with NaN values of type real.
1005 // This will help catch mistakes in case someone reads memory that they shouldn't.
1006 auto fillBeginAddr =
1007 Min(reinterpret_cast<size_type>(rangeEnd),
1008 RoundUp(reinterpret_cast<size_type>(rangeBegin), alignof(real)));
1009 auto fillEndAddr =
1010 Max(reinterpret_cast<size_type>(rangeBegin),
1011 RoundDown(reinterpret_cast<size_type>(rangeEnd), alignof(real)));
1012 auto* fillBegin = reinterpret_cast<std::byte*>(fillBeginAddr);
1013 auto* fillEnd = reinterpret_cast<std::byte*>(fillEndAddr);
1014 auto const nan = std::numeric_limits<real>::signaling_NaN();
1015 for (auto it = fillBegin; it < fillEnd; it += sizeof(nan)) {
1016 // Use memcpy instead of dereferencing a real* because of strict aliasing rules.
1017 memcpy(it, &nan, sizeof(nan));
1018 }
1019 // Because of alignment, the fill range might be smaller than the input range.
1020 // In that case, write zeros to the remaining bytes on either end.
1021 auto paddingSizeFront = fillBegin - reinterpret_cast<std::byte*>(rangeBegin);
1022 if (paddingSizeFront) {
1023 memset(rangeBegin, 0, paddingSizeFront);
1024 }
1025 auto paddingSizeBack = reinterpret_cast<std::byte const*>(rangeEnd) - fillEnd;
1026 if (paddingSizeBack) {
1027 memset(fillEnd, 0, paddingSizeBack);
1028 }
1029#endif // MOCHI_DARRAY_DEBUG
1030 }
1031
1032 static void DefaultConstructRange(T* rangeBegin, T const* rangeEnd) {
1033 if constexpr (std::is_trivially_default_constructible_v<T>) {
1034 auto numBytes =
1035 reinterpret_cast<std::intptr_t>(rangeEnd) - reinterpret_cast<std::intptr_t>(rangeBegin);
1036 memset(rangeBegin, 0, numBytes); // Zero-initialize
1037 } else {
1038 for (auto* it = rangeBegin; it < rangeEnd; ++it) {
1039 new (it) T; // Default construct
1040 }
1041 }
1042 }
1043
1044 static void CopyConstructRangeFromValue(T* rangeBegin, T const* rangeEnd, T const& value) {
1045 // TODO: Use superdex::Fill for cases where copy construction is equivalent to copy assignment.
1046 for (auto* it = rangeBegin; it < rangeEnd; ++it) {
1047 new (it) T(value); // Copy construct
1048 }
1049 }
1050
1051 template <class InputIt>
1052 static void CopyConstructRange(T* dstBegin, InputIt srcBegin, InputIt srcEnd) {
1053 // TODO(C++20): Replace by std::iter_value_t<InputIt>.
1054 using SrcT = std::remove_cv_t<typename std::iterator_traits<InputIt>::value_type>;
1055 if constexpr (
1056 std::is_trivially_copyable_v<T> && std::is_copy_assignable_v<T> &&
1057 std::is_same_v<SrcT, T>) {
1058 std::copy(srcBegin, srcEnd, dstBegin); // Copy assign. Works for any iterator type.
1059 } else {
1060 auto src = srcBegin;
1061 auto dst = dstBegin;
1062 for (; src != srcEnd; ++src, ++dst) {
1063 new (dst) T(*src); // Copy construct
1064 }
1065 }
1066 }
1067
1068 static void MoveConstructRange(T* dstBegin, T* srcBegin, size_type count) {
1069 if constexpr (std::is_trivially_move_constructible_v<T>) {
1070 if (count > 0)
1071 MOCHI_LIKELY {
1072 memcpy(dstBegin, srcBegin, count * sizeof(T));
1073 }
1074 } else {
1075 for (auto i = 0; i < count; ++i) {
1076 new (&dstBegin[i]) T(std::move(srcBegin[i])); // Move construct
1077 }
1078 }
1079 }
1080
1081 static void Destroy(T* it) {
1082 if constexpr (!std::is_trivially_destructible_v<T>) {
1083 it->~T();
1084 }
1085 DebugFillWithNaN(it, it + 1);
1086 }
1087
1088 static void DestroyRange(T* rangeBegin, T const* rangeEnd) {
1089 if constexpr (!std::is_trivially_destructible_v<T>) {
1090 if (rangeEnd > rangeBegin)
1091 MOCHI_LIKELY {
1092 // Destroy in reverse order in case of FILO allocator used within element
1093 auto* it = const_cast<T*>(rangeEnd);
1094 while (it != rangeBegin) {
1095 --it;
1096 it->~T();
1097 }
1098 }
1099 }
1100 DebugFillWithNaN(rangeBegin, rangeEnd);
1101 }
1102
1103#if MOCHI_DARRAY_DEBUG
1104 template <class InputIt>
1105 bool IsInThisArray(InputIt rangeBegin, InputIt rangeEnd) const {
1106 if constexpr (std::is_pointer_v<std::remove_reference_t<InputIt>>) {
1107 // Return true if the input range overlaps the memory used by this DynamicArray
1108 return !(
1109 (reinterpret_cast<T const*>(rangeBegin)) >= _endCapacity ||
1110 (reinterpret_cast<T const*>(rangeEnd) <= _begin));
1111 } else {
1112 // If input iterators are used (not raw pointers) then we'll assume they come from somewhere
1113 // else since DynamicArray does not use an iterator class.
1114 return false;
1115 }
1116 }
1117
1118 bool IsInThisArray(T const* ptr) const {
1119 return (ptr >= _begin) && (ptr < _end);
1120 }
1121#endif // MOCHI_DARRAY_DEBUG
1122
1123 // When overallocating, start with at least this capacity.
1124 static constexpr size_type kGrowthPatternStartSize = 8;
1125
1126 T* _begin = nullptr;
1127 T* _end = nullptr;
1128 T* _endCapacity = nullptr;
1129 Allocator* _allocator = GetDefaultAllocator();
1130};
1131
1132/**
1133 Utility Functions
1134*/
1135
1136// Specialization of Append found in container_utils.h
1137template <typename T, typename InBeginIterator, typename InEndIterator>
1139Append(DynamicArray<T>& out, InBeginIterator const& inBegin, InEndIterator const& inEnd) {
1140 out.append(inBegin, inEnd);
1141}
1142
1143// Specialization of Append found in container_utils.h
1144template <typename T, typename InContainer>
1145MOCHI_FORCE_INLINE void Append(DynamicArray<T>& out, InContainer const& inContainer) {
1146 out.append(std::begin(inContainer), std::end(inContainer));
1147}
1148
1149// Specialization of Append found in container_utils.h
1150// For each input value, add valueToAdd and append to result to the output.
1151template <typename T, typename ContainerIn>
1152void AppendSum(DynamicArray<T>& out, ContainerIn const& in, T valueToAdd) {
1153 if (valueToAdd == T(0)) {
1154 Append(out, in);
1155 } else {
1156 auto outIndex = out.size();
1157 out.resize_noinit(outIndex + std::size(in));
1158 for (auto const& x : in) {
1159 out[outIndex++] = x + valueToAdd;
1160 }
1161 }
1162}
1163
1164/**
1165 Type Traits
1166*/
1167
1168namespace details {
1169template <class ContainerT>
1170struct IsDynamicArrayDef : public std::false_type {};
1171template <class T>
1172struct IsDynamicArrayDef<DynamicArray<T>> : public std::true_type {};
1173} // namespace details
1174
1175// IsDynamicArray<ContainerT> is true iff ContainerT is a type of the form DynamicArray<T>
1176template <class ContainerT>
1177static constexpr bool kIsDynamicArray = // Note: nvcc thinks details without superdex:: is ambiguous.
1178 superdex::details::IsDynamicArrayDef<std::decay_t<ContainerT>>::value;
1179
1180// Class Template Argument Deduction (CTAD) guides
1181template <
1182 typename InputIt,
1183 MOCHI_CONCEPT((std::is_base_of_v<
1184 std::input_iterator_tag,
1185 typename std::iterator_traits<InputIt>::iterator_category>))>
1188
1189} // namespace superdex
1190
1191// Reflection support
1192#if MOCHI_USE_REFLECTION
1193template <class T>
1194struct SReflectTypeTraits<superdex::DynamicArray<T>> {
1195 static constexpr SReflect::CoreType coreType = SReflect::CoreType::CT_array;
1196 static SReflect::ArrayTypeInfo const& GetTypeInfo() {
1197 using ArrT = superdex::DynamicArray<T>;
1198 static auto* s_typeInfo =
1199 SReflect::MakeDynamicArrayTypeInfo<SReflect::VectorTypeInfo<ArrT>, ArrT, T>(
1200 "superdex::DynamicArray", true);
1201 return *s_typeInfo;
1202 }
1203};
1204#endif // MOCHI_USE_REFLECTION
Virtual interface class for memory allocation and deallocation.
Definition allocator.h:47
A dynamically resizable array with syntax and behavior similar to std::pmr::vector.
DynamicArray()=default
Construct an empty DynamicArray with the default allocator.
DynamicArray(size_type size, FromT const &defaultValue, Allocator *allocator=GetDefaultAllocator())
Construct a DynamicArray filled with copies of the specified value.
size_type size() const
Get the number of elements in the array.
void append(InputIt rangeBegin, InputIt rangeEnd)
Add values to the end of this DynamicArray by copying them from the half-open range [rangeBegin,...
T const & back() const
Get a const reference to the last element.
const_iterator cbegin() const
Get a const iterator pointing to the beginning of the array.
DynamicArray(DynamicArray const &other)
Copy construct from another DynamicArray.
T & push_back()
Default construct a new element at the end of the array.
void reset(Args &&... args)
Reset the state of this DynamicArray using the arguments from any constructor.
iterator begin()
Get an iterator pointing to the beginning of the array.
void resize_noinit(size_type newSize)
Set the size but do not initialize any new elements.
DynamicArray & operator=(DynamicArray const &other)
Copy Assignment: Replace the contents of this DynamicArray by copying values from another one.
void push_back(T &&value)
Move the value to a new element at the end of the array.
DynamicArray & operator=(std::initializer_list< T > const &list)
Copy Assignment: Replace the contents of this DynamicArray by copying values from a std::initializer_...
Allocator * get_allocator() const
Get a pointer to the polymorphic allocator.
void resize(size_type newSize)
Set the size.
void resize(size_type newSize, T const &value)
Set the size.
T & operator[](size_type i)
Get a reference to the value at index i.
DynamicArray(size_type size, Allocator *allocator=GetDefaultAllocator())
Construct a DynamicArray with an initial size.
DynamicArray & operator=(DynamicArray &&other)
Move Assignment: Replace the contents of this DynamicArray by moving memory or values from another on...
bool operator==(DynamicArray const &other) const
Comparison returns true if the other array has the same size and all values are equal.
T const & front() const
Get a const reference to the first element.
DynamicArray(std::initializer_list< T > const &list, Allocator *allocator=GetDefaultAllocator())
Construct a DynamicArray and copy values from a std::initializer_list.
T const & operator[](size_type i) const
Get a reference to the value at index i.
T const * data() const
Get a const pointer to the first element.
T * data()
Get a pointer to the first element.
void erase(const_iterator it_)
Remove one element from this array.
void pop_back()
Remove the last element of the array (must not be empty).
void push_back(T const &value)
Copy the value to a new element at the end of the array.
void erase(const_iterator rangeBegin_, const_iterator rangeEnd_)
Remove the half-open range [rangeBegin, rangeEnd) from this array.
void assign(InputIt rangeBegin, InputIt rangeEnd)
Replace the contents of this DynamicArray by copying values from the half-open range [rangeBegin,...
const_iterator cend() const
Get a const iterator pointing to the next element AFTER the end of the array.
void erase_unordered(iterator it)
Remove one element from this array and swap the last element into its place.
DynamicArray(DynamicArray &&other) noexcept
Move construct from another DynamicArray.
bool empty() const
Return true if the array is empty (zero size).
size_type capacity() const
Get the number of elements that could fit in the array without allocating more memory.
DynamicArray(DynamicArray const &other, Allocator *allocator)
Construct a DynamicArray and copy values from another one.
const_iterator end() const
Get a const iterator pointing to the next element AFTER the end of the array.
void clear()
Set the size to zero.
void shrink_to_fit()
Ensure that the capacity (memory allocated) is no larger than the size (memory used).
bool operator!=(DynamicArray const &other) const
Comparison returns false unless the other array has the same size and all values are equal.
T & front()
Get a reference to the first element.
iterator end()
Get an iterator pointing to the next element AFTER the end of the array.
DynamicArray(Allocator *allocator)
Construct an empty DynamicArray with a specific allocator.
T & back()
Get a reference to the last element.
void reserve(size_type newCapacity)
Ensure that this array has at least the specified capacity.
DynamicArray & operator=(InputContainerT const &other)
Copy Assignment: Replace the contents of this DynamicArray by copying values from another iterable co...
~DynamicArray()
Destroy the DynamicArray object and all of its elements.
DynamicArray(InputIt rangeBegin, InputIt rangeEnd, Allocator *allocator=GetDefaultAllocator())
Construct a DynamicArray and copy values from the half-open range: [rangeBegin, rangeEnd).
DynamicArray(DynamicArray &&other, Allocator *allocator)
Construct a DynamicArray by moving memory or values from another one.
void emplace_back(Args &&... args)
Emplace a new element at the end of the array.
DynamicArray(InputContainerT const &other, Allocator *allocator=GetDefaultAllocator())
Construct a DynamicArray and copy values from another iterable container type like superdex::Span,...
void append(InputContainerT const &container)
Add values to the end of this DynamicArray by copying them from another iterable container.
const_iterator begin() const
Get a const iterator pointing to the beginning of the array.
#define MOCHI_DARRAY_ASSERT_FORWARD_ITERATOR(IteratorType)
#define MOCHI_DARRAY_ASSERT(cond,...)
#define MOCHI_CONCEPT(a)
#define MOCHI_UNLIKELY
#define MOCHI_FORCE_INLINE
#define MOCHI_LIKELY
constexpr T const & Min(T const &a, T const &b)
constexpr T RoundDown(T numToRound, T multiple)
constexpr T RoundUp(T numToRound, T multiple)
void Append(DynamicArray< T > &out, InBeginIterator const &inBegin, InEndIterator const &inEnd)
Utility Functions.
constexpr bool kIsResizeNoInitSafe
Helper variable template for IsResizeNoInitSafe.
DynamicArray(InputIt, InputIt, Allocator *=GetDefaultAllocator()) -> DynamicArray< typename std::iterator_traits< InputIt >::value_type >
Allocator * GetDefaultAllocator()
Get an instance of the DefaultAllocator class.
constexpr T const & Max(T const &a, T const &b)
void AppendSum(DynamicArray< T > &out, ContainerIn const &in, T valueToAdd)
Trait to determine if a type is safe for use with DynamicArray::resize_noinit.