Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
typed_buffer_array.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <helios/assert.hpp>
4#include <helios/container/details/typed_buffer_common.hpp>
5
6#include <algorithm>
7#include <concepts>
8#include <cstddef>
9#include <cstring>
10#include <initializer_list>
11#include <iterator>
12#include <memory>
13#include <memory_resource>
14#include <new>
15#include <ranges>
16#include <span>
17#include <type_traits>
18#include <utility>
19#include <vector>
20
21namespace helios::container {
22
23/**
24 * @brief Type-erased sequential byte storage for a single type (array variant).
25 * @details Stores multiple instances of a type in a contiguous
26 * `std::vector<std::byte>` buffer. The stored type is not fixed at class
27 * instantiation but is verified at runtime. Object lifetimes are properly
28 * managed. Trivially-copyable types are fast-pathed.
29 * @tparam Allocator The allocator type for the byte storage (default:
30 * `std::allocator<std::byte>`)
31 */
32template <typename Allocator = std::allocator<std::byte>>
34public:
35 using allocator_type = Allocator;
36 using size_type = size_t;
37 using difference_type = ptrdiff_t;
39
40 using ContainerType = std::vector<std::byte, allocator_type>;
41
42private:
43 using TypeInfo = details::TypeBufferInfo;
44
45public:
46 /**
47 * @brief Default constructor.
48 * @details Creates an empty storage with no associated type.
49 */
50 constexpr TypedBufferArray() noexcept(
51 std::is_nothrow_default_constructible_v<ContainerType>) = default;
52
53 /**
54 * @brief Constructs an empty storage with a custom allocator.
55 * @param alloc Allocator instance to use for the underlying container
56 */
57 explicit constexpr TypedBufferArray(const allocator_type& alloc) noexcept(
58 std::is_nothrow_constructible_v<ContainerType, const allocator_type&>)
59 : storage_(alloc) {}
60
61 /**
62 * @brief Constructs an empty storage from a PMR memory resource.
63 * @details Enabled only when `allocator_type` is constructible from
64 * `std::pmr::memory_resource*`.
65 * @param resource Memory resource used to construct allocator
66 */
67 explicit constexpr TypedBufferArray(
68 std::pmr::memory_resource*
69 resource) noexcept(std::
70 is_nothrow_constructible_v<
72 std::pmr::memory_resource*>)
73 requires std::constructible_from<allocator_type, std::pmr::memory_resource*>
74 : TypedBufferArray(allocator_type{resource}) {}
75
76 TypedBufferArray(std::nullptr_t) = delete;
77
78 /**
79 * @brief Constructs storage with count default-inserted elements of type `T`.
80 * @tparam T The type to store
81 * @param count Number of elements to default-construct
82 */
83 template <TypedBufferStorable T>
84 requires std::default_initializable<T>
85 explicit constexpr TypedBufferArray(size_type count);
86
87 /**
88 * @brief Constructs storage with count copies of value.
89 * @tparam T The type to store
90 * @param count Number of elements
91 * @param value Value to copy
92 */
93 template <TypedBufferStorable T>
94 requires std::copy_constructible<T>
95 constexpr TypedBufferArray(size_type count, const T& value);
96
97 /**
98 * @brief Constructs storage from iterator range.
99 * @tparam InputIt Input iterator type
100 * @param first Beginning of range
101 * @param last End of range
102 */
103 template <std::input_iterator InputIt>
104 requires TypedBufferStorable<std::iter_value_t<InputIt>>
105 constexpr TypedBufferArray(InputIt first, InputIt last);
106
107 /**
108 * @brief Constructs storage from initializer list.
109 * @tparam T The type to store
110 * @param init Initializer list
111 */
112 template <TypedBufferStorable T>
113 requires std::copy_constructible<T>
114 constexpr TypedBufferArray(std::initializer_list<T> init)
115 : TypedBufferArray(init.begin(), init.end()) {}
116
117 constexpr TypedBufferArray(const TypedBufferArray& other);
119 const TypedBufferArray& other,
120 const allocator_type&
121 alloc) noexcept(std::
122 is_nothrow_constructible_v<
124 constexpr TypedBufferArray(TypedBufferArray&& other) noexcept(
125 std::is_nothrow_move_constructible_v<ContainerType>);
127 TypedBufferArray&& other,
128 const allocator_type&
129 alloc) noexcept(std::
130 is_nothrow_constructible_v<
132 constexpr ~TypedBufferArray() noexcept { DestroyAll(); }
133
135 constexpr TypedBufferArray& operator=(TypedBufferArray&& other) noexcept(
136 std::is_nothrow_move_assignable_v<ContainerType>);
137
138 /**
139 * @brief Assignment from initializer list.
140 * @tparam T The type to store
141 * @param init Initializer list
142 * @return Reference to this
143 */
144 template <TypedBufferStorable T>
145 requires std::copy_constructible<T>
146 constexpr TypedBufferArray& operator=(std::initializer_list<T> init);
147
148 /**
149 * @brief Changes the stored type, destroying current elements but keeping
150 * memory.
151 * @details Destroys all current elements and resets size to 0. Memory is
152 * retained.
153 * @tparam T The new type to store
154 */
155 template <TypedBufferStorable T>
156 constexpr void ChangeType() noexcept;
157
158 /**
159 * @brief Resets storage to empty state with no associated type.
160 * @details Destroys all elements and clears type information.
161 */
162 constexpr void Reset() noexcept;
163
164 /**
165 * @brief Appends a value to the end.
166 * @warning Triggers assertion if type mismatch and storage is not empty.
167 * @tparam T The type of value (must match stored type)
168 * @param value The value to push
169 */
170 template <TypedBufferStorable T>
171 void PushBack(T&& value);
172
173 /**
174 * @brief Constructs an element in-place at the end.
175 * @warning Triggers assertion if type mismatch and storage is not empty.
176 * @tparam T The type to construct (must match stored type)
177 * @tparam Args Constructor argument types
178 * @param args Arguments forwarded to the constructor
179 * @return Reference to the emplaced element
180 */
181 template <TypedBufferStorable T, typename... Args>
182 requires std::constructible_from<T, Args...>
183 T& EmplaceBack(Args&&... args);
184
185 /**
186 * @brief Constructs an element in-place at the specified position.
187 * @warning Triggers assertion if pos > `Size()` or type mismatch and storage
188 * is not empty.
189 * @tparam T The type to construct (must match stored type)
190 * @tparam Args Constructor argument types
191 * @param pos Position (element index)
192 * @param args Arguments forwarded to the constructor
193 * @return Reference to the emplaced element
194 */
195 template <TypedBufferStorable T, typename... Args>
196 requires std::constructible_from<T, Args...>
197 T& Emplace(size_type pos, Args&&... args);
198
199 /**
200 * @brief Inserts a value at the specified position.
201 * @warning Triggers assertion if pos > `Size()` or type mismatch and storage
202 * is not empty.
203 * @tparam T The type to insert (must match stored type)
204 * @param pos Position (element index)
205 * @param value Value to insert
206 * @return Reference to the inserted element
207 */
208 template <TypedBufferStorable T>
209 T& Insert(size_type pos, T&& value) {
210 return Emplace<T>(pos, std::forward<T>(value));
211 }
212
213 /**
214 * @brief Inserts count copies of value at the specified position.
215 * @warning Triggers assertion if pos > `Size()` or type mismatch and storage
216 * is not empty.
217 * @tparam T The type to insert (must match stored type)
218 * @param pos Position (element index)
219 * @param count Number of copies
220 * @param value Value to copy
221 * @return Pointer to the first inserted element, or `nullptr` if count == 0
222 */
223 template <TypedBufferStorable T>
224 requires std::copy_constructible<T>
225 T* Insert(size_type pos, size_type count, const T& value);
226
227 /**
228 * @brief Inserts elements from iterator range at the specified position.
229 * @warning Triggers assertion if pos > `Size()` or type mismatch and storage
230 * is not empty.
231 * @tparam InputIt Input iterator type
232 * @param pos Position (element index)
233 * @param first Beginning of range
234 * @param last End of range
235 * @return Pointer to the first inserted element, or `nullptr` if range is
236 * empty
237 */
238 template <std::input_iterator InputIt>
239 requires TypedBufferStorable<std::iter_value_t<InputIt>>
240 auto Insert(size_type pos, InputIt first, InputIt last)
241 -> std::iter_value_t<InputIt>*;
242
243 /**
244 * @brief Inserts elements from initializer list at the specified position.
245 * @warning Triggers assertion if pos > `Size()` or type mismatch and storage
246 * is not empty.
247 * @tparam T The type to insert (must match stored type)
248 * @param pos Position (element index)
249 * @param init Initializer list
250 * @return Pointer to the first inserted element, or `nullptr` if list is
251 * empty
252 */
253 template <TypedBufferStorable T>
254 requires std::copy_constructible<T>
255 T* Insert(size_type pos, std::initializer_list<T> init) {
256 return Insert(pos, init.begin(), init.end());
257 }
258
259 /**
260 * @brief Appends elements from a range to the end.
261 * @warning Triggers assertion if type mismatch and storage is not empty.
262 * @tparam Range Range type
263 * @param range The range to append
264 */
265 template <std::ranges::input_range Range>
266 requires TypedBufferStorable<std::ranges::range_value_t<Range>>
267 void AppendRange(Range&& range);
268
269 /**
270 * @brief Inserts elements from a range at the specified position.
271 * @warning Triggers assertion if pos > `Size()` or type mismatch and storage
272 * is not empty.
273 * @tparam Range Range type
274 * @param pos Position (element index)
275 * @param range The range to insert
276 * @return Pointer to the first inserted element, or `nullptr` if range is
277 * empty
278 */
279 template <std::ranges::input_range Range>
280 requires TypedBufferStorable<std::ranges::range_value_t<Range>>
281 auto InsertRange(size_type pos, Range&& range)
282 -> std::ranges::range_value_t<Range>*;
283
284 /**
285 * @brief Removes the last stored value.
286 * @warning Triggers assertion if storage is empty.
287 */
288 constexpr void PopBack() noexcept;
289
290 /**
291 * @brief Erases the element at the specified position.
292 * @warning Triggers assertion if pos >= `Size()` or storage has no type.
293 * @param pos Position (element index) to erase
294 */
295 constexpr void Erase(size_type pos);
296
297 /**
298 * @brief Erases elements in the range [first_pos, last_pos).
299 * @warning Triggers assertion if range is invalid or storage has no type.
300 * @param first_pos Beginning of range (element index)
301 * @param last_pos End of range (element index, exclusive)
302 */
303 constexpr void Erase(size_type first_pos, size_type last_pos);
304
305 /// @brief Clears all stored data, but retains type information.
306 constexpr void Clear() noexcept;
307
308 /**
309 * @brief Reserves space for at least the specified number of elements.
310 * @warning Triggers assertion if no type is set and count > 0.
311 * @param count Number of elements to reserve space for
312 */
313 constexpr void Reserve(size_type count);
314
315 /// @brief Reduces capacity to fit the current size.
316 constexpr void ShrinkToFit();
317
318 /**
319 * @brief Resizes the storage to contain count elements (default-initialized).
320 * @warning Triggers assertion if type mismatch and storage is not empty.
321 * @tparam T The type of elements (must match stored type)
322 * @param count New size
323 */
324 template <TypedBufferStorable T>
325 requires std::default_initializable<T>
326 void Resize(size_type count);
327
328 /**
329 * @brief Resizes the storage to contain count elements.
330 * @warning Triggers assertion if type mismatch and storage is not empty.
331 * @tparam T The type of elements (must match stored type)
332 * @param count New size
333 * @param value Value to fill new elements with
334 */
335 template <TypedBufferStorable T>
336 requires std::copy_constructible<T>
337 void Resize(size_type count, const T& value);
338
339 /**
340 * @brief Access element at index.
341 * @warning Triggers assertion if index >= `Size()` or type mismatch.
342 * @tparam T The type of elements (must match stored type)
343 * @param index Element index
344 * @return Reference to element
345 */
346 template <TypedBufferStorable T>
347 [[nodiscard]] T& At(size_type index) noexcept;
348
349 /**
350 * @brief Access element at index (const).
351 * @warning Triggers assertion if index >= `Size()` or type mismatch.
352 * @tparam T The type of elements (must match stored type)
353 * @param index Element index
354 * @return Const reference to element
355 */
356 template <TypedBufferStorable T>
357 [[nodiscard]] const T& At(size_type index) const noexcept;
358
359 /**
360 * @brief Access the first element.
361 * @warning Triggers assertion if storage is empty or type mismatch.
362 * @tparam T The type of elements (must match stored type)
363 * @return Reference to the first element
364 */
365 template <TypedBufferStorable T>
366 [[nodiscard]] T& Front() noexcept;
367
368 /**
369 * @brief Access the first element (const).
370 * @warning Triggers assertion if storage is empty or type mismatch.
371 * @tparam T The type of elements (must match stored type)
372 * @return Const reference to the first element
373 */
374 template <TypedBufferStorable T>
375 [[nodiscard]] const T& Front() const noexcept;
376
377 /**
378 * @brief Access the last element.
379 * @warning Triggers assertion if storage is empty or type mismatch.
380 * @tparam T The type of elements (must match stored type)
381 * @return Reference to the last element
382 */
383 template <TypedBufferStorable T>
384 [[nodiscard]] T& Back() noexcept;
385
386 /**
387 * @brief Access the last element (const).
388 * @warning Triggers assertion if storage is empty or type mismatch.
389 * @tparam T The type of elements (must match stored type)
390 * @return Const reference to the last element
391 */
392 template <TypedBufferStorable T>
393 [[nodiscard]] const T& Back() const noexcept;
394
395 /**
396 * @brief Merges all elements from another `TypedBufferArray` into this one.
397 * @details Appends all elements from `other` to the end of this buffer.
398 * If this buffer is empty and has no type, it takes ownership of `other`'s
399 * state. After merging, `other` is left in a valid but empty state.
400 * @warning Triggers assertion if both buffers have types set and they don't
401 * match.
402 * @tparam OtherAllocator The allocator type of the other buffer
403 * @param other The buffer to merge from (will be left empty)
404 */
405 template <typename OtherAllocator>
406 constexpr void Merge(const TypedBufferArray<OtherAllocator>& other);
407
408 /**
409 * @brief Merges all elements from another `TypedBufferArray` into this one.
410 * @details Rvalue overload that consumes the source buffer.
411 * @tparam OtherAllocator The allocator type of the other buffer
412 * @param other The buffer to merge from (will be left empty)
413 */
414 template <typename OtherAllocator>
415 constexpr void Merge(TypedBufferArray<OtherAllocator>&& other);
416
417 /**
418 * @brief Swaps contents with another storage.
419 * @param other Storage to swap with
420 */
421 constexpr void Swap(TypedBufferArray& other) noexcept(
422 std::is_nothrow_swappable_v<ContainerType>);
423
424 /**
425 * @brief Swaps two elements within the buffer at the given indices
426 * (type-erased).
427 * @details For trivially copyable types, performs a direct byte-level swap.
428 * For non-trivially copyable types, performs a 3-step move-construct swap via
429 * temporary storage.
430 * @warning Triggers assertion if storage has no type, or index >= `Size()`.
431 * @param index Index of the first element
432 * @param other_index Index of the second element
433 */
434 void Swap(size_type index, size_type other_index);
435
436 /**
437 * @brief Swaps two elements within the buffer at the given indices (typed).
438 * @details Uses `std::swap`.
439 * @warning Triggers assertion if storage has no type, stored type doesn't
440 * match `T`, or index >= `Size()`.
441 * @tparam T The type of stored elements (must match stored type)
442 * @param index Index of the first element
443 * @param other_index Index of the second element
444 */
445 template <TypedBufferStorable T>
446 void Swap(size_type index, size_type other_index);
447
448 friend constexpr void
450 std::is_nothrow_swappable_v<ContainerType>) {
451 lhs.Swap(rhs);
452 }
453
454 /**
455 * @brief Checks if the storage is empty.
456 * @return True if empty, false if a value is stored
457 */
458 [[nodiscard]] constexpr bool Empty() const noexcept { return size_ == 0; }
459
460 /**
461 * @brief Checks if a type is currently associated with this storage.
462 * @return True if a type is set, false if no type is set
463 */
464 [[nodiscard]] constexpr bool HasType() const noexcept {
465 return type_info_.IsValid();
466 }
467
468 /**
469 * @brief Checks if the stored type matches `T`.
470 * @tparam T The type to check
471 * @return true if types match or no type is set
472 */
473 template <TypedBufferStorable T>
474 [[nodiscard]] constexpr bool IsType() const noexcept {
475 return !type_info_.IsValid() || type_info_.type_index == TypeIndexOf<T>();
476 }
477
478 /**
479 * @brief Gets the type index for type `T`.
480 * @tparam T The type to get index for
481 * @return Type index
482 */
483 template <TypedBufferStorable T>
484 [[nodiscard]] static consteval TypeIndex TypeIndexOf() noexcept {
486 }
487
488 /**
489 * @brief Gets the current stored type index.
490 * @return Type index of the stored type, or invalid index if no type is set
491 */
492 [[nodiscard]] constexpr TypeIndex StoredTypeId() const noexcept {
493 return type_info_.type_index;
494 }
495
496 /** @brief Returns the number of stored elements.
497 * @return Number of elements currently stored, or 0 if no type is set (even
498 * if bytes are present)
499 */
500 [[nodiscard]] constexpr size_type Size() const noexcept { return size_; }
501
502 /**
503 * @brief Returns the number of bytes occupied by stored elements.
504 * @warning If no type is set, this will return 0 even if there are bytes in
505 * the storage.
506 */
507 [[nodiscard]] constexpr size_type SizeBytes() const noexcept {
508 return size_ * type_info_.element_size;
509 }
510
511 /**
512 * @brief Returns the size of each element in bytes.
513 * @return Size of each element in bytes, or 0 if no type is set
514 */
515 [[nodiscard]] constexpr size_type ElementSize() const noexcept {
516 return type_info_.element_size;
517 }
518
519 /**
520 * @brief Returns the capacity in number of elements.
521 * @return Capacity in number of elements, or 0 if no type is set
522 */
523 [[nodiscard]] constexpr size_type Capacity() const noexcept;
524
525 /**
526 * @brief Retrieves a span of all stored values.
527 * @warning Triggers assertion if type mismatch.
528 * @tparam T The type of elements (must match stored type)
529 * @return A span containing all values, or empty span if no values stored
530 */
531 template <TypedBufferStorable T>
532 [[nodiscard]] auto Data() noexcept -> std::span<T>;
533
534 /**
535 * @brief Retrieves a const span of all stored values.
536 * @warning Triggers assertion if type mismatch.
537 * @tparam T The type of elements (must match stored type)
538 * @return A const span containing all values, or empty span if no values
539 * stored
540 */
541 template <TypedBufferStorable T>
542 [[nodiscard]] auto Data() const noexcept -> std::span<const T>;
543
544 /**
545 * @brief Retrieves a const byte span of all stored data (type-erased).
546 * @return A const byte span containing all stored bytes, or empty span if no
547 * type is set (even if bytes are present)
548 */
549 [[nodiscard]] constexpr auto Bytes() const noexcept
550 -> std::span<const std::byte> {
551 return {storage_.data(), SizeBytes()};
552 }
553
554 /**
555 * @brief Returns iterator to the beginning.
556 * @tparam T The type of elements (must match stored type)
557 * @return Iterator to the first element, or `nullptr` if no elements stored
558 */
559 template <TypedBufferStorable T>
560 [[nodiscard]] T* begin() noexcept {
561 return DataPtr<T>();
562 }
563
564 /**
565 * @brief Returns const iterator to the beginning.
566 * @tparam T The type of elements (must match stored type)
567 * @return Const iterator to the first element, or `nullptr` if no elements
568 * stored
569 */
570 template <TypedBufferStorable T>
571 [[nodiscard]] const T* begin() const noexcept {
572 return DataPtr<T>();
573 }
574
575 /**
576 * @brief Returns const iterator to the beginning.
577 * @tparam T The type of elements (must match stored type)
578 * @return Const iterator to the first element, or `nullptr` if no elements
579 * stored
580 */
581 template <TypedBufferStorable T>
582 [[nodiscard]] const T* cbegin() const noexcept {
583 return DataPtr<T>();
584 }
585
586 /**
587 * @brief Returns iterator past the last element.
588 * @tparam T The type of elements (must match stored type)
589 * @return Iterator past the last element, or `nullptr` if no elements stored
590 */
591 template <TypedBufferStorable T>
592 [[nodiscard]] T* end() noexcept {
593 return DataPtr<T>() + size_;
594 }
595
596 /**
597 * @brief Returns const iterator past the last element.
598 * @tparam T The type of elements (must match stored type)
599 * @return Const iterator past the last element, or `nullptr` if no elements
600 * stored
601 */
602 template <TypedBufferStorable T>
603 [[nodiscard]] const T* end() const noexcept {
604 return DataPtr<T>() + size_;
605 }
606
607 /**
608 * @brief Returns const iterator past the last element.
609 * @tparam T The type of elements (must match stored type)
610 * @return Const iterator past the last element, or `nullptr` if no elements
611 * stored
612 */
613 template <TypedBufferStorable T>
614 [[nodiscard]] const T* cend() const noexcept {
615 return DataPtr<T>() + size_;
616 }
617
618 /**
619 * @brief Returns reverse iterator to the last element.
620 * @tparam T The type of elements (must match stored type)
621 * @return Reverse iterator to the last element, or `nullptr` if no elements
622 * stored
623 */
624 template <TypedBufferStorable T>
625 [[nodiscard]] auto rbegin() noexcept -> std::reverse_iterator<T*> {
626 return std::reverse_iterator<T*>(end<T>());
627 }
628
629 /**
630 * @brief Returns const reverse iterator to the last element.
631 * @tparam T The type of elements (must match stored type)
632 * @return Const reverse iterator to the last element, or `nullptr` if no
633 * elements stored
634 */
635 template <TypedBufferStorable T>
636 [[nodiscard]] auto rbegin() const noexcept
637 -> std::reverse_iterator<const T*> {
638 return std::reverse_iterator<const T*>(end<T>());
639 }
640
641 /**
642 * @brief Returns const reverse iterator to the last element.
643 * @tparam T The type of elements (must match stored type)
644 * @return Const reverse iterator to the last element, or `nullptr` if no
645 * elements stored
646 */
647 template <TypedBufferStorable T>
648 [[nodiscard]] auto crbegin() const noexcept
649 -> std::reverse_iterator<const T*> {
650 return std::reverse_iterator<const T*>(cend<T>());
651 }
652
653 /**
654 * @brief Returns reverse iterator before the first element.
655 * @tparam T The type of elements (must match stored type)
656 * @return Reverse iterator before the first element, or `nullptr` if no
657 * elements stored
658 */
659 template <TypedBufferStorable T>
660 [[nodiscard]] auto rend() noexcept -> std::reverse_iterator<T*> {
661 return std::reverse_iterator<T*>(begin<T>());
662 }
663
664 /**
665 * @brief Returns const reverse iterator before the first element.
666 * @tparam T The type of elements (must match stored type)
667 * @return Const reverse iterator before the first element, or `nullptr` if no
668 * elements stored
669 */
670 template <TypedBufferStorable T>
671 [[nodiscard]] auto rend() const noexcept -> std::reverse_iterator<const T*> {
672 return std::reverse_iterator<const T*>(begin<T>());
673 }
674
675 /**
676 * @brief Returns const reverse iterator before the first element.
677 * @tparam T The type of elements (must match stored type)
678 * @return Const reverse iterator before the first element, or `nullptr` if no
679 * elements stored
680 */
681 template <TypedBufferStorable T>
682 [[nodiscard]] auto crend() const noexcept -> std::reverse_iterator<const T*> {
683 return std::reverse_iterator<const T*>(cbegin<T>());
684 }
685
686private:
687 template <typename OtherA>
688 friend class TypedBufferArray;
689
690 template <TypedBufferStorable T>
691 [[nodiscard]] T* DataPtr() noexcept;
692
693 template <TypedBufferStorable T>
694 [[nodiscard]] const T* DataPtr() const noexcept;
695
696 [[nodiscard]] constexpr void* RawDataPtr() noexcept {
697 return storage_.empty() ? nullptr : static_cast<void*>(storage_.data());
698 }
699
700 [[nodiscard]] constexpr const void* RawDataPtr() const noexcept {
701 return storage_.empty() ? nullptr
702 : static_cast<const void*>(storage_.data());
703 }
704
705 constexpr void DestroyAll() noexcept;
706 constexpr void DestroyRange(size_type first_idx, size_type last_idx) noexcept;
707 constexpr void GrowIfNeeded(size_type additional_count);
708 constexpr void ShiftRight(size_type pos, size_type count);
709 constexpr void ShiftLeft(size_type pos, size_type count);
710
711 /// @brief Verifies or sets type for operations.
712 template <TypedBufferStorable T>
713 constexpr void EnsureType();
714
715 TypeInfo type_info_{};
716 ContainerType storage_;
717 size_type size_ = 0;
718};
719
720template <typename Allocator>
721template <TypedBufferStorable T>
722 requires std::default_initializable<T>
724 if (count == 0) [[unlikely]] {
725 return;
726 }
727
728 type_info_ = TypeInfo::template From<T>();
729 storage_.resize(count * sizeof(T));
730
731 auto* ptr = static_cast<T*>(RawDataPtr());
732 std::uninitialized_default_construct_n(ptr, count);
733 size_ = count;
734}
735
736template <typename Allocator>
737template <TypedBufferStorable T>
738 requires std::copy_constructible<T>
740 const T& value) {
741 using DecayedT = std::remove_cvref_t<T>;
742
743 if (count == 0) [[unlikely]] {
744 return;
745 }
746
747 type_info_ = TypeInfo::template From<DecayedT>();
748 storage_.resize(count * sizeof(DecayedT));
749
750 auto* ptr = static_cast<DecayedT*>(RawDataPtr());
751 std::uninitialized_fill_n(ptr, count, value);
752 size_ = count;
753}
754
755template <typename Allocator>
756template <std::input_iterator InputIt>
757 requires TypedBufferStorable<std::iter_value_t<InputIt>>
759 InputIt last) {
760 using T = std::iter_value_t<InputIt>;
761
762 if (first == last) [[unlikely]] {
763 return;
764 }
765
766 type_info_ = TypeInfo::template From<T>();
767
768 if constexpr (std::forward_iterator<InputIt>) {
769 const auto count = static_cast<size_type>(std::distance(first, last));
770 storage_.resize(count * sizeof(T));
771 auto* ptr = static_cast<T*>(RawDataPtr());
772 std::uninitialized_copy(first, last, ptr);
773 size_ = count;
774 } else {
775 for (; first != last; ++first) {
776 EmplaceBack<T>(*first);
777 }
778 }
779}
780
781template <typename Allocator>
783 const TypedBufferArray& other) {
784 if (other.Empty()) [[unlikely]] {
785 type_info_ = other.type_info_;
786 return;
787 }
788
789 type_info_ = other.type_info_;
791 type_info_.is_copy_constructible,
792 "Cannot copy TypedBufferArray: stored type is not copy constructible!");
793
794 storage_.resize(other.SizeBytes());
795
796 if (type_info_.is_trivially_copyable) {
797 std::memcpy(storage_.data(), other.storage_.data(), other.SizeBytes());
798 } else {
799 type_info_.copy_construct(RawDataPtr(), other.RawDataPtr(), other.size_);
800 }
801 size_ = other.size_;
802}
803
804template <typename Allocator>
806 const TypedBufferArray& other,
807 const allocator_type&
808 alloc) noexcept(std::is_nothrow_constructible_v<ContainerType,
809 const allocator_type&>)
810 : storage_(alloc) {
811 if (other.Empty()) [[unlikely]] {
812 type_info_ = other.type_info_;
813 return;
814 }
815
816 type_info_ = other.type_info_;
818 type_info_.is_copy_constructible,
819 "Cannot copy TypedBufferArray: stored type is not copy constructible!");
820
821 storage_.resize(other.SizeBytes());
822
823 if (type_info_.is_trivially_copyable) {
824 std::memcpy(storage_.data(), other.storage_.data(), other.SizeBytes());
825 } else {
826 type_info_.copy_construct(RawDataPtr(), other.RawDataPtr(), other.size_);
827 }
828 size_ = other.size_;
829}
830
831template <typename Allocator>
834 other) noexcept(std::is_nothrow_move_constructible_v<ContainerType>)
835 : type_info_(other.type_info_),
836 storage_(std::move(other.storage_)),
837 size_(other.size_) {
838 other.size_ = 0;
839 other.type_info_ = TypeInfo{};
840}
841
842template <typename Allocator>
844 TypedBufferArray&& other,
845 const allocator_type&
846 alloc) noexcept(std::is_nothrow_constructible_v<ContainerType,
847 const allocator_type&>)
848 : type_info_(other.type_info_), storage_(alloc), size_(0) {
849 if (other.size_ == 0) [[unlikely]] {
850 other.type_info_.Reset();
851 return;
852 }
853
854 if (storage_.get_allocator() == other.storage_.get_allocator()) {
855 storage_ = std::move(other.storage_);
856 size_ = other.size_;
857 other.size_ = 0;
858 other.type_info_.Reset();
859 return;
860 }
861
862 storage_.resize(other.SizeBytes());
863
864 if (type_info_.is_trivially_copyable) {
865 std::memcpy(storage_.data(), other.storage_.data(), other.SizeBytes());
866 } else if (type_info_.move_construct != nullptr) {
867 type_info_.move_construct(RawDataPtr(), other.RawDataPtr(), other.size_);
868 other.DestroyAll();
869 } else if (type_info_.copy_construct != nullptr) {
870 type_info_.copy_construct(RawDataPtr(), other.RawDataPtr(), other.size_);
871 other.DestroyAll();
872 }
873
874 size_ = other.size_;
875 other.storage_.clear();
876 other.size_ = 0;
877 other.type_info_.Reset();
878}
879
880template <typename Allocator>
882 const TypedBufferArray& other) -> TypedBufferArray& {
883 if (this == &other) [[unlikely]] {
884 return *this;
885 }
886
887 Clear();
888
889 if (other.Empty()) [[unlikely]] {
890 type_info_ = other.type_info_;
891 return *this;
892 }
893
894 type_info_ = other.type_info_;
896 type_info_.is_copy_constructible,
897 "Cannot copy TypedBufferArray: stored type is not copy constructible!");
898
899 storage_.resize(other.SizeBytes());
900
901 if (type_info_.is_trivially_copyable) {
902 std::memcpy(storage_.data(), other.storage_.data(), other.SizeBytes());
903 } else {
904 type_info_.copy_construct(RawDataPtr(), other.RawDataPtr(), other.size_);
905 }
906 size_ = other.size_;
907
908 return *this;
909}
910
911template <typename Allocator>
912constexpr auto
914 std::is_nothrow_move_assignable_v<ContainerType>) -> TypedBufferArray& {
915 if (this == &other) [[unlikely]] {
916 return *this;
917 }
918
919 DestroyAll();
920 type_info_ = other.type_info_;
921 storage_ = std::move(other.storage_);
922 size_ = other.size_;
923 other.size_ = 0;
924 other.type_info_.Reset();
925
926 return *this;
927}
928
929template <typename Allocator>
930template <TypedBufferStorable T>
931 requires std::copy_constructible<T>
933 std::initializer_list<T> init) -> TypedBufferArray& {
934 Clear();
935
936 if (init.size() == 0) [[unlikely]] {
937 return *this;
938 }
939
940 type_info_ = TypeInfo::template From<T>();
941 storage_.resize(init.size() * sizeof(T));
942
943 auto* ptr = static_cast<T*>(RawDataPtr());
944 std::uninitialized_copy(init.begin(), init.end(), ptr);
945 size_ = init.size();
946
947 return *this;
948}
949
950template <typename Allocator>
951template <TypedBufferStorable T>
953 DestroyAll();
954 size_ = 0;
955 type_info_ = TypeInfo::template From<T>();
956}
957
958template <typename Allocator>
959constexpr void TypedBufferArray<Allocator>::Reset() noexcept {
960 DestroyAll();
961 storage_.clear();
962 size_ = 0;
963 type_info_.Reset();
964}
965
966template <typename Allocator>
967template <TypedBufferStorable T>
969 using DecayedT = std::remove_cvref_t<T>;
970
971 EnsureType<DecayedT>();
972 GrowIfNeeded(1);
973 std::construct_at(DataPtr<DecayedT>() + size_, std::forward<T>(value));
974 ++size_;
975}
976
977template <typename Allocator>
978template <TypedBufferStorable T, typename... Args>
979 requires std::constructible_from<T, Args...>
981 EnsureType<T>();
982 GrowIfNeeded(1);
983 auto* ptr = DataPtr<T>() + size_;
984 std::construct_at(ptr, std::forward<Args>(args)...);
985 ++size_;
986 return *ptr;
987}
988
989template <typename Allocator>
990template <TypedBufferStorable T, typename... Args>
991 requires std::constructible_from<T, Args...>
992inline T& TypedBufferArray<Allocator>::Emplace(size_type pos, Args&&... args) {
993 HELIOS_ASSERT(pos <= size_, "Emplace position '{}' is out of bounds!", pos);
994 EnsureType<T>();
995
996 if (pos == size_) {
997 return EmplaceBack<T>(std::forward<Args>(args)...);
998 }
999
1000 GrowIfNeeded(1);
1001 ShiftRight(pos, 1);
1002 auto* ptr = DataPtr<T>() + pos;
1003 std::construct_at(ptr, std::forward<Args>(args)...);
1004 ++size_;
1005 return *ptr;
1006}
1007
1008template <typename Allocator>
1009template <TypedBufferStorable T>
1010 requires std::copy_constructible<T>
1012 const T& value) {
1013 using DecayedT = std::remove_cvref_t<T>;
1014
1015 HELIOS_ASSERT(pos <= size_, "Insert position '{}' is out of bounds!", pos);
1016
1017 if (count == 0) [[unlikely]] {
1018 return nullptr;
1019 }
1020
1021 EnsureType<DecayedT>();
1022 GrowIfNeeded(count);
1023
1024 if (pos < size_) {
1025 ShiftRight(pos, count);
1026 }
1027
1028 auto* ptr = DataPtr<DecayedT>() + pos;
1029 std::uninitialized_fill_n(ptr, count, value);
1030 size_ += count;
1031
1032 return ptr;
1033}
1034
1035template <typename Allocator>
1036template <std::input_iterator InputIt>
1037 requires TypedBufferStorable<std::iter_value_t<InputIt>>
1038inline auto TypedBufferArray<Allocator>::Insert(size_type pos, InputIt first,
1039 InputIt last)
1040 -> std::iter_value_t<InputIt>* {
1041 using T = std::iter_value_t<InputIt>;
1042
1043 HELIOS_ASSERT(pos <= size_, "Insert position '{}' is out of bounds!", pos);
1044
1045 if (first == last) [[unlikely]] {
1046 return nullptr;
1047 }
1048
1049 EnsureType<T>();
1050
1051 if constexpr (std::forward_iterator<InputIt>) {
1052 const auto count = static_cast<size_type>(std::distance(first, last));
1053 GrowIfNeeded(count);
1054
1055 if (pos < size_) {
1056 ShiftRight(pos, count);
1057 }
1058
1059 auto* ptr = DataPtr<T>() + pos;
1060 std::uninitialized_copy(first, last, ptr);
1061 size_ += count;
1062 return ptr;
1063 } else {
1064 auto current_pos = pos;
1065 for (; first != last; ++first, ++current_pos) {
1066 GrowIfNeeded(1);
1067 if (current_pos < size_) {
1068 ShiftRight(current_pos, 1);
1069 }
1070 std::construct_at(DataPtr<T>() + current_pos, *first);
1071 ++size_;
1072 }
1073 return DataPtr<T>() + pos;
1074 }
1075}
1076
1077template <typename Allocator>
1078template <std::ranges::input_range Range>
1079 requires TypedBufferStorable<std::ranges::range_value_t<Range>>
1081 using T = std::ranges::range_value_t<Range>;
1082 EnsureType<T>();
1083
1084 if constexpr (std::ranges::sized_range<Range>) {
1085 const size_type count = std::ranges::size(range);
1086 if (count == 0) [[unlikely]] {
1087 return;
1088 }
1089 GrowIfNeeded(count);
1090 }
1091
1092 for (auto&& elem : std::forward<Range>(range)) {
1093 GrowIfNeeded(1);
1094 std::construct_at(DataPtr<T>() + size_, std::forward<decltype(elem)>(elem));
1095 ++size_;
1096 }
1097}
1098
1099template <typename Allocator>
1100template <std::ranges::input_range Range>
1101 requires TypedBufferStorable<std::ranges::range_value_t<Range>>
1103 Range&& range)
1104 -> std::ranges::range_value_t<Range>* {
1105 using T = std::ranges::range_value_t<Range>;
1106
1107 HELIOS_ASSERT(pos <= size_, "InsertRange position '{}' is out of bounds!",
1108 pos);
1109 EnsureType<T>();
1110
1111 if constexpr (std::ranges::sized_range<Range>) {
1112 const size_type count = std::ranges::size(range);
1113 if (count == 0) [[unlikely]] {
1114 return nullptr;
1115 }
1116
1117 GrowIfNeeded(count);
1118 if (pos < size_) {
1119 ShiftRight(pos, count);
1120 }
1121
1122 size_type idx = 0;
1123 for (auto&& elem : std::forward<Range>(range)) {
1124 std::construct_at(DataPtr<T>() + pos + idx,
1125 std::forward<decltype(elem)>(elem));
1126 ++idx;
1127 }
1128 size_ += count;
1129 return DataPtr<T>() + pos;
1130 } else {
1131 auto current_pos = pos;
1132 for (auto&& elem : std::forward<Range>(range)) {
1133 GrowIfNeeded(1);
1134 if (current_pos < size_) {
1135 ShiftRight(current_pos, 1);
1136 }
1137 std::construct_at(DataPtr<T>() + current_pos,
1138 std::forward<decltype(elem)>(elem));
1139 ++size_;
1140 ++current_pos;
1141 }
1142 return pos < current_pos ? DataPtr<T>() + pos : nullptr;
1143 }
1144}
1145
1146template <typename Allocator>
1147constexpr void TypedBufferArray<Allocator>::PopBack() noexcept {
1148 HELIOS_ASSERT(!Empty(), "Cannot pop from empty TypedBufferArray!");
1149 --size_;
1150 DestroyRange(size_, size_ + 1);
1151}
1152
1153template <typename Allocator>
1155 HELIOS_ASSERT(pos < size_, "Erase position '{}' out of bounds!", pos);
1156 HELIOS_ASSERT(HasType(), "Cannot erase from storage with no type!");
1157
1158 DestroyRange(pos, pos + 1);
1159 if (pos + 1 < size_) {
1160 ShiftLeft(pos + 1, 1);
1161 }
1162 --size_;
1163}
1164
1165template <typename Allocator>
1167 size_type last_pos) {
1168 HELIOS_ASSERT(first_pos <= size_, "Erase range start '{}' is out of bounds!",
1169 first_pos);
1170 HELIOS_ASSERT(last_pos <= size_, "Erase range end '{}' is out of bounds!",
1171 last_pos);
1172 HELIOS_ASSERT(first_pos <= last_pos, "Invalid erase range!");
1173
1174 if (first_pos == last_pos) [[unlikely]] {
1175 return;
1176 }
1177
1178 HELIOS_ASSERT(HasType(), "Cannot erase from storage with no type!");
1179
1180 const size_type count = last_pos - first_pos;
1181 DestroyRange(first_pos, last_pos);
1182
1183 if (last_pos < size_) {
1184 ShiftLeft(last_pos, count);
1185 }
1186 size_ -= count;
1187}
1188
1189template <typename Allocator>
1190constexpr void TypedBufferArray<Allocator>::Clear() noexcept {
1191 DestroyAll();
1192 storage_.clear();
1193 size_ = 0;
1194}
1195
1196template <typename Allocator>
1198 if (count == 0) [[unlikely]] {
1199 return;
1200 }
1201
1202 HELIOS_ASSERT(HasType(), "Cannot reserve without a type set!");
1203
1204 const size_type required_bytes = count * type_info_.element_size;
1205 if (required_bytes <= storage_.size()) {
1206 return;
1207 }
1208
1209 if (!type_info_.is_trivially_copyable && size_ > 0) {
1210 ContainerType new_storage(storage_.get_allocator());
1211 new_storage.resize(required_bytes);
1212
1213 if (type_info_.relocate != nullptr) {
1214 type_info_.relocate(new_storage.data(), storage_.data(), size_);
1215 } else if (type_info_.move_construct != nullptr) {
1216 type_info_.move_construct(new_storage.data(), storage_.data(), size_);
1217 DestroyAll();
1218 } else if (type_info_.copy_construct != nullptr) {
1219 type_info_.copy_construct(new_storage.data(), storage_.data(), size_);
1220 DestroyAll();
1221 }
1222
1223 storage_ = std::move(new_storage);
1224 } else {
1225 storage_.resize(required_bytes);
1226 }
1227}
1228
1229template <typename Allocator>
1231 if (!HasType() || size_ == 0) {
1232 storage_.clear();
1233 storage_.shrink_to_fit();
1234 return;
1235 }
1236
1237 const auto used_bytes = SizeBytes();
1238 if (used_bytes < storage_.size()) {
1239 ContainerType new_storage(storage_.get_allocator());
1240 new_storage.resize(used_bytes);
1241
1242 if (type_info_.is_trivially_copyable) {
1243 std::memcpy(new_storage.data(), storage_.data(), used_bytes);
1244 } else if (type_info_.move_construct != nullptr) {
1245 type_info_.move_construct(new_storage.data(), storage_.data(), size_);
1246 DestroyAll();
1247 } else if (type_info_.copy_construct != nullptr) {
1248 type_info_.copy_construct(new_storage.data(), storage_.data(), size_);
1249 DestroyAll();
1250 }
1251
1252 storage_ = std::move(new_storage);
1253 }
1254 storage_.shrink_to_fit();
1255}
1256
1257template <typename Allocator>
1258template <TypedBufferStorable T>
1259 requires std::default_initializable<T>
1261 EnsureType<T>();
1262 if (count < size_) {
1263 DestroyRange(count, size_);
1264 size_ = count;
1265 } else if (count > size_) {
1266 GrowIfNeeded(count - size_);
1267 auto* ptr = DataPtr<T>() + size_;
1268 std::uninitialized_default_construct_n(ptr, count - size_);
1269 size_ = count;
1270 }
1271}
1272
1273template <typename Allocator>
1274template <TypedBufferStorable T>
1275 requires std::copy_constructible<T>
1277 const T& value) {
1278 using DecayedT = std::remove_cvref_t<T>;
1279
1280 EnsureType<DecayedT>();
1281 if (count < size_) {
1282 DestroyRange(count, size_);
1283 size_ = count;
1284 } else if (count > size_) {
1285 GrowIfNeeded(count - size_);
1286 auto* ptr = DataPtr<DecayedT>() + size_;
1287 std::uninitialized_fill_n(ptr, count - size_, value);
1288 size_ = count;
1289 }
1290}
1291
1292template <typename Allocator>
1293template <TypedBufferStorable T>
1295 HELIOS_ASSERT(index < size_, "Index '{}' is out of bounds!", index);
1297 !type_info_.IsValid() || type_info_.type_index == TypeIndexOf<T>(),
1298 "Type mismatch: storage contains different type!");
1299 return *(DataPtr<T>() + index);
1300}
1301
1302template <typename Allocator>
1303template <TypedBufferStorable T>
1305 size_type index) const noexcept {
1306 HELIOS_ASSERT(index < size_, "Index '{}' is out of bounds!", index);
1308 !type_info_.IsValid() || type_info_.type_index == TypeIndexOf<T>(),
1309 "Type mismatch: storage contains different type!");
1310 return *(DataPtr<T>() + index);
1311}
1312
1313template <typename Allocator>
1314template <TypedBufferStorable T>
1316 HELIOS_ASSERT(!Empty(), "Cannot access front of empty TypedBufferArray!");
1318 !type_info_.IsValid() || type_info_.type_index == TypeIndexOf<T>(),
1319 "Type mismatch: storage contains different type!");
1320 return *DataPtr<T>();
1321}
1322
1323template <typename Allocator>
1324template <TypedBufferStorable T>
1325inline const T& TypedBufferArray<Allocator>::Front() const noexcept {
1326 HELIOS_ASSERT(!Empty(), "Cannot access front of empty TypedBufferArray!");
1328 !type_info_.IsValid() || type_info_.type_index == TypeIndexOf<T>(),
1329 "Type mismatch: storage contains different type!");
1330 return *DataPtr<T>();
1331}
1332
1333template <typename Allocator>
1334template <TypedBufferStorable T>
1336 HELIOS_ASSERT(!Empty(), "Cannot access back of empty TypedBufferArray!");
1338 !type_info_.IsValid() || type_info_.type_index == TypeIndexOf<T>(),
1339 "Type mismatch: storage contains different type!");
1340 return *(DataPtr<T>() + size_ - 1);
1341}
1342
1343template <typename Allocator>
1344template <TypedBufferStorable T>
1345inline const T& TypedBufferArray<Allocator>::Back() const noexcept {
1346 HELIOS_ASSERT(!Empty(), "Cannot access back of empty TypedBufferArray!");
1348 !type_info_.IsValid() || type_info_.type_index == TypeIndexOf<T>(),
1349 "Type mismatch: storage contains different type!");
1350 return *(DataPtr<T>() + size_ - 1);
1351}
1352
1353template <typename Allocator>
1354template <typename OtherAllocator>
1356 const TypedBufferArray<OtherAllocator>& other) {
1357 if (other.Empty()) [[unlikely]] {
1358 if (!other.type_info_.IsValid()) {
1359 return;
1360 }
1361 if (!type_info_.IsValid()) {
1362 type_info_ = other.type_info_;
1363 }
1364 return;
1365 }
1366
1367 if (!HasType()) {
1368 type_info_ = other.type_info_;
1369 }
1370
1372 type_info_.type_index == other.type_info_.type_index,
1373 "Type mismatch: Cannot merge TypedBufferArray of different types!");
1374
1375 const size_type other_size = other.size_;
1376 GrowIfNeeded(other_size);
1377
1378 if (type_info_.is_trivially_copyable) {
1379 std::memcpy(static_cast<std::byte*>(RawDataPtr()) + SizeBytes(),
1380 other.RawDataPtr(), other_size * type_info_.element_size);
1381 } else if (type_info_.copy_construct) {
1382 type_info_.copy_construct(
1383 static_cast<std::byte*>(RawDataPtr()) + SizeBytes(), other.RawDataPtr(),
1384 other_size);
1385 } else {
1386 HELIOS_ASSERT(false,
1387 "Cannot merge from const source: stored type is not copy "
1388 "constructible!");
1389 }
1390
1391 size_ += other_size;
1392}
1393
1394template <typename Allocator>
1395template <typename OtherAllocator>
1398 if (other.Empty()) [[unlikely]] {
1399 if (!other.type_info_.IsValid()) {
1400 return;
1401 }
1402 if (!type_info_.IsValid()) {
1403 type_info_ = other.type_info_;
1404 }
1405 other.type_info_.Reset();
1406 return;
1407 }
1408
1409 if constexpr (std::same_as<Allocator, OtherAllocator>) {
1410 if (!HasType()) {
1411 *this = std::move(other);
1412 return;
1413 }
1414 } else {
1415 if (!HasType()) {
1416 type_info_ = other.type_info_;
1417 }
1418 }
1419
1421 type_info_.type_index == other.type_info_.type_index,
1422 "Type mismatch: Cannot merge TypedBufferArray of different types!");
1423
1424 const size_type other_size = other.size_;
1425 GrowIfNeeded(other_size);
1426
1427 if (type_info_.is_trivially_copyable) {
1428 std::memcpy(static_cast<std::byte*>(RawDataPtr()) + SizeBytes(),
1429 other.RawDataPtr(), other_size * type_info_.element_size);
1430 } else if (type_info_.move_construct) {
1431 type_info_.move_construct(
1432 static_cast<std::byte*>(RawDataPtr()) + SizeBytes(), other.RawDataPtr(),
1433 other_size);
1434 } else if (type_info_.copy_construct) {
1435 type_info_.copy_construct(
1436 static_cast<std::byte*>(RawDataPtr()) + SizeBytes(), other.RawDataPtr(),
1437 other_size);
1438 } else {
1440 false,
1441 "Cannot merge: stored type is neither move nor copy constructible!");
1442 }
1443
1444 size_ += other_size;
1445 other.DestroyAll();
1446 other.storage_.clear();
1447 other.size_ = 0;
1448 other.type_info_.Reset();
1449}
1450
1451template <typename Allocator>
1452constexpr void
1454 std::is_nothrow_swappable_v<ContainerType>) {
1455 std::swap(type_info_, other.type_info_);
1456 std::swap(storage_, other.storage_);
1457 std::swap(size_, other.size_);
1458}
1459
1460template <typename Allocator>
1462 size_type other_index) {
1464 "Cannot swap elements in storage without a type set!");
1465 HELIOS_ASSERT(index < size_, "Swap index '{}' is out of bounds!", index);
1466 HELIOS_ASSERT(other_index < size_, "Swap other_index '{}' is out of bounds!",
1467 other_index);
1468
1469 if (index == other_index) [[unlikely]] {
1470 return;
1471 }
1472
1473 auto* base = static_cast<std::byte*>(RawDataPtr());
1474 auto* first = base + (index * type_info_.element_size);
1475 auto* second = base + (other_index * type_info_.element_size);
1476
1477 if (type_info_.is_trivially_copyable) {
1478 std::swap_ranges(first, first + type_info_.element_size, second);
1479 } else {
1480 HELIOS_ASSERT(type_info_.move_construct,
1481 "Type must be move constructible to swap elements!");
1482
1483 auto alloc = storage_.get_allocator();
1484 using alloc_traits = std::allocator_traits<allocator_type>;
1485
1486 auto* temp = alloc_traits::allocate(alloc, type_info_.element_size);
1487
1488 try {
1489 type_info_.move_construct(temp, first, 1);
1490 if (type_info_.destroy != nullptr) {
1491 type_info_.destroy(first, 1);
1492 }
1493
1494 type_info_.move_construct(first, second, 1);
1495 if (type_info_.destroy != nullptr) {
1496 type_info_.destroy(second, 1);
1497 }
1498
1499 type_info_.move_construct(second, temp, 1);
1500 if (type_info_.destroy != nullptr) {
1501 type_info_.destroy(temp, 1);
1502 }
1503 } catch (...) {
1504 alloc_traits::deallocate(alloc, temp, type_info_.element_size);
1505 throw;
1506 }
1507
1508 alloc_traits::deallocate(alloc, temp, type_info_.element_size);
1509 }
1510}
1511
1512template <typename Allocator>
1513template <TypedBufferStorable T>
1515 size_type other_index) {
1517 "Cannot swap elements in storage without a type set!");
1518 HELIOS_ASSERT(type_info_.type_index == TypeIndexOf<T>(),
1519 "Type mismatch: stored type does not match!");
1520 HELIOS_ASSERT(index < size_, "Swap index '{}' is out of bounds!", index);
1521 HELIOS_ASSERT(other_index < size_, "Swap other_index '{}' is out of bounds!",
1522 other_index);
1523
1524 if (index == other_index) [[unlikely]] {
1525 return;
1526 }
1527
1528 std::swap(At<T>(index), At<T>(other_index));
1529}
1530
1531template <typename Allocator>
1532constexpr auto TypedBufferArray<Allocator>::Capacity() const noexcept
1533 -> size_type {
1534 if (!HasType()) [[unlikely]] {
1535 return 0;
1536 }
1537 return storage_.capacity() / type_info_.element_size;
1538}
1539
1540template <typename Allocator>
1541template <TypedBufferStorable T>
1542inline auto TypedBufferArray<Allocator>::Data() noexcept -> std::span<T> {
1544 !type_info_.IsValid() || type_info_.type_index == TypeIndexOf<T>(),
1545 "Type mismatch: storage contains different type!");
1546 return {DataPtr<T>(), size_};
1547}
1548
1549template <typename Allocator>
1550template <TypedBufferStorable T>
1551inline auto TypedBufferArray<Allocator>::Data() const noexcept
1552 -> std::span<const T> {
1554 !type_info_.IsValid() || type_info_.type_index == TypeIndexOf<T>(),
1555 "Type mismatch: storage contains different type!");
1556 return {DataPtr<T>(), size_};
1557}
1558
1559template <typename Allocator>
1560template <TypedBufferStorable T>
1561inline T* TypedBufferArray<Allocator>::DataPtr() noexcept {
1563 !type_info_.IsValid() || type_info_.type_index == TypeIndexOf<T>(),
1564 "Type mismatch: storage contains different type!");
1565 if (storage_.empty()) [[unlikely]] {
1566 return nullptr;
1567 }
1568 return std::launder(reinterpret_cast<T*>(storage_.data()));
1569}
1570
1571template <typename Allocator>
1572template <TypedBufferStorable T>
1573inline const T* TypedBufferArray<Allocator>::DataPtr() const noexcept {
1575 !type_info_.IsValid() || type_info_.type_index == TypeIndexOf<T>(),
1576 "Type mismatch: storage contains different type!");
1577 if (storage_.empty()) [[unlikely]] {
1578 return nullptr;
1579 }
1580 return std::launder(reinterpret_cast<const T*>(storage_.data()));
1581}
1582
1583template <typename Allocator>
1584constexpr void TypedBufferArray<Allocator>::DestroyAll() noexcept {
1585 if (size_ > 0 && type_info_.destroy != nullptr) {
1586 type_info_.destroy(RawDataPtr(), size_);
1587 }
1588}
1589
1590template <typename Allocator>
1591constexpr void TypedBufferArray<Allocator>::DestroyRange(
1592 size_type first, size_type last) noexcept {
1593 if (first >= last || type_info_.destroy == nullptr) {
1594 return;
1595 }
1596
1597 auto* ptr =
1598 static_cast<std::byte*>(RawDataPtr()) + (first * type_info_.element_size);
1599 type_info_.destroy(ptr, last - first);
1600}
1601
1602template <typename Allocator>
1603constexpr void TypedBufferArray<Allocator>::GrowIfNeeded(
1604 size_type additional_count) {
1605 HELIOS_ASSERT(HasType(), "Cannot grow storage without a type set!");
1606
1607 const size_type required_bytes =
1608 (size_ + additional_count) * type_info_.element_size;
1609 if (required_bytes <= storage_.size()) {
1610 return;
1611 }
1612
1613 size_type new_capacity = storage_.size();
1614 if (new_capacity == 0) {
1615 new_capacity = type_info_.element_size;
1616 }
1617 while (new_capacity < required_bytes) {
1618 new_capacity =
1619 new_capacity +
1620 std::max(new_capacity / 2, size_type{1}); // 1.5x growth, min +1
1621 }
1622
1623 if (!type_info_.is_trivially_copyable && size_ > 0) {
1624 ContainerType new_storage(storage_.get_allocator());
1625 new_storage.resize(new_capacity);
1626
1627 if (type_info_.relocate != nullptr) {
1628 type_info_.relocate(new_storage.data(), storage_.data(), size_);
1629 } else if (type_info_.move_construct != nullptr) {
1630 type_info_.move_construct(new_storage.data(), storage_.data(), size_);
1631 DestroyAll();
1632 } else if (type_info_.copy_construct != nullptr) {
1633 type_info_.copy_construct(new_storage.data(), storage_.data(), size_);
1634 DestroyAll();
1635 }
1636
1637 storage_ = std::move(new_storage);
1638 } else {
1639 storage_.resize(new_capacity);
1640 }
1641}
1642
1643template <typename Allocator>
1644constexpr void TypedBufferArray<Allocator>::ShiftRight(size_type pos,
1645 size_type count) {
1646 HELIOS_ASSERT(HasType(), "Cannot shift in storage without a type set!");
1647 HELIOS_ASSERT(pos <= size_, "ShiftRight position '{}' is out of bounds!",
1648 pos);
1649
1650 const size_type required_bytes = (size_ + count) * type_info_.element_size;
1651 if (required_bytes > storage_.size()) {
1652 storage_.resize(required_bytes);
1653 }
1654
1655 const size_type elements_to_move = size_ - pos;
1656 if (elements_to_move == 0) {
1657 return;
1658 }
1659
1660 auto* src =
1661 static_cast<std::byte*>(RawDataPtr()) + (pos * type_info_.element_size);
1662 auto* dest = src + (count * type_info_.element_size);
1663
1664 if (type_info_.is_trivially_copyable) {
1665 std::memmove(dest, src, elements_to_move * type_info_.element_size);
1666 } else if (type_info_.relocate != nullptr) {
1667 for (size_type idx = elements_to_move; idx > 0; --idx) {
1668 auto* elem_src = src + ((idx - 1) * type_info_.element_size);
1669 auto* elem_dest = dest + ((idx - 1) * type_info_.element_size);
1670 type_info_.relocate(elem_dest, elem_src, 1);
1671 }
1672 } else {
1673 HELIOS_ASSERT(type_info_.move_construct,
1674 "Type must be move constructible for shift operations!");
1675 for (size_type idx = elements_to_move; idx > 0; --idx) {
1676 auto* elem_src = src + ((idx - 1) * type_info_.element_size);
1677 auto* elem_dest = dest + ((idx - 1) * type_info_.element_size);
1678 type_info_.move_construct(elem_dest, elem_src, 1);
1679 if (type_info_.destroy != nullptr) {
1680 type_info_.destroy(elem_src, 1);
1681 }
1682 }
1683 }
1684}
1685
1686template <typename Allocator>
1687constexpr void TypedBufferArray<Allocator>::ShiftLeft(size_type pos,
1688 size_type count) {
1689 HELIOS_ASSERT(HasType(), "Cannot shift in storage without a type set!");
1690 HELIOS_ASSERT(pos >= count, "ShiftLeft position '{}' is invalid!", pos);
1691 HELIOS_ASSERT(pos <= size_, "ShiftLeft position '{}' is out of bounds!", pos);
1692
1693 const size_type elements_to_move = size_ - pos;
1694 if (elements_to_move == 0) {
1695 return;
1696 }
1697
1698 auto* src =
1699 static_cast<std::byte*>(RawDataPtr()) + (pos * type_info_.element_size);
1700 auto* dest = src - (count * type_info_.element_size);
1701
1702 if (type_info_.is_trivially_copyable) {
1703 std::memmove(dest, src, elements_to_move * type_info_.element_size);
1704 } else if (type_info_.relocate != nullptr) {
1705 for (size_type idx = 0; idx < elements_to_move; ++idx) {
1706 auto* elem_src = src + (idx * type_info_.element_size);
1707 auto* elem_dest = dest + (idx * type_info_.element_size);
1708 type_info_.relocate(elem_dest, elem_src, 1);
1709 }
1710 } else {
1711 HELIOS_ASSERT(type_info_.move_construct,
1712 "Type must be move constructible for shift operations!");
1713 for (size_type idx = 0; idx < elements_to_move; ++idx) {
1714 auto* elem_src = src + (idx * type_info_.element_size);
1715 auto* elem_dest = dest + (idx * type_info_.element_size);
1716 type_info_.move_construct(elem_dest, elem_src, 1);
1717 if (type_info_.destroy != nullptr) {
1718 type_info_.destroy(elem_src, 1);
1719 }
1720 }
1721 }
1722}
1723
1724template <typename Allocator>
1725template <TypedBufferStorable T>
1726constexpr void TypedBufferArray<Allocator>::EnsureType() {
1727 if (!type_info_.IsValid()) {
1728 type_info_ = TypeInfo::template From<T>();
1729 } else {
1730 HELIOS_ASSERT(type_info_.type_index == TypeIndexOf<T>(),
1731 "Type mismatch: TypedBufferArray contains different type!");
1732 }
1733}
1734
1737
1738/**
1739 * @brief Erases all elements equal to value from the TypedBufferArray.
1740 * @details Uses the swap-then-pop pattern for each match. Element order is not
1741 * preserved.
1742 * @tparam T The element type
1743 * @tparam Allocator The allocator type
1744 * @param storage The buffer to erase from
1745 * @param value The value to erase
1746 * @return Number of elements erased
1747 */
1748template <TypedBufferStorable T, typename Allocator>
1749 requires(std::is_class_v<Allocator> &&
1750 !std::same_as<std::remove_cvref_t<T>,
1752inline auto erase(TypedBufferArray<Allocator>& storage, const T& value) ->
1754 using DecayedT = std::remove_cvref_t<T>;
1755 using size_type = typename TypedBufferArray<Allocator>::size_type;
1756
1757 size_type erased = 0;
1758 size_type i = 0;
1759 while (i < storage.Size()) {
1760 if (storage.template At<DecayedT>(i) == value) {
1761 storage.template Swap<DecayedT>(i, storage.Size() - 1);
1762 storage.PopBack();
1763 ++erased;
1764 } else {
1765 ++i;
1766 }
1767 }
1768
1769 return erased;
1770}
1771
1772/**
1773 * @brief Erases the element at position pos from the TypedBufferArray
1774 * (swap-then-pop, O(1), unstable).
1775 * @tparam Allocator The allocator type
1776 * @param storage The buffer to erase from
1777 * @param pos Index of the element to erase
1778 */
1779template <typename Allocator>
1780 requires std::is_class_v<Allocator>
1783 storage.Swap(pos, storage.Size() - 1);
1784 storage.PopBack();
1785}
1786
1787/**
1788 * @brief Erases all elements satisfying the predicate from the
1789 * TypedBufferArray.
1790 * @details Uses the swap-then-pop pattern. Element order is not preserved.
1791 * @tparam T The element type
1792 * @tparam Allocator The allocator type
1793 * @tparam Pred Predicate type
1794 * @param storage The buffer to erase from
1795 * @param pred Predicate to test elements
1796 * @return Number of elements erased
1797 */
1798template <TypedBufferStorable T, typename Allocator, typename Pred>
1799 requires(std::is_class_v<Allocator> && std::predicate<Pred, const T&>)
1800inline auto erase_if(TypedBufferArray<Allocator>& storage, Pred pred) ->
1802 using size_type = typename TypedBufferArray<Allocator>::size_type;
1803
1804 size_type erased = 0;
1805 size_type idx = 0;
1806 while (idx < storage.Size()) {
1807 if (pred(storage.template At<T>(idx))) {
1808 storage.template Swap<T>(idx, storage.Size() - 1);
1809 storage.PopBack();
1810 ++erased;
1811 } else {
1812 ++idx;
1813 }
1814 }
1815
1816 return erased;
1817}
1818
1819} // namespace helios::container
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
Type-erased sequential byte storage for a single type (array variant).
constexpr TypedBufferArray(const TypedBufferArray &other)
constexpr void ChangeType() noexcept
Changes the stored type, destroying current elements but keeping memory.
constexpr size_type SizeBytes() const noexcept
Returns the number of bytes occupied by stored elements.
auto crbegin() const noexcept -> std::reverse_iterator< const T * >
Returns const reverse iterator to the last element.
constexpr TypedBufferArray(const TypedBufferArray &other, const allocator_type &alloc) noexcept(std::is_nothrow_constructible_v< ContainerType, const allocator_type & >)
constexpr TypeIndex StoredTypeId() const noexcept
Gets the current stored type index.
constexpr bool Empty() const noexcept
Checks if the storage is empty.
constexpr bool HasType() const noexcept
Checks if a type is currently associated with this storage.
const T * end() const noexcept
Returns const iterator past the last element.
constexpr auto Bytes() const noexcept -> std::span< const std::byte >
auto InsertRange(size_type pos, Range &&range) -> std::ranges::range_value_t< Range > *
Inserts elements from a range at the specified position.
T * Insert(size_type pos, std::initializer_list< T > init)
Inserts elements from initializer list at the specified position.
auto rbegin() const noexcept -> std::reverse_iterator< const T * >
Returns const reverse iterator to the last element.
constexpr size_type ElementSize() const noexcept
Returns the size of each element in bytes.
static consteval TypeIndex TypeIndexOf() noexcept
Gets the type index for type T.
constexpr size_type Size() const noexcept
Returns the number of stored elements.
const T * cend() const noexcept
Returns const iterator past the last element.
auto rbegin() noexcept -> std::reverse_iterator< T * >
Returns reverse iterator to the last element.
constexpr TypedBufferArray() noexcept(std::is_nothrow_default_constructible_v< ContainerType >)=default
Default constructor.
auto rend() const noexcept -> std::reverse_iterator< const T * >
Returns const reverse iterator before the first element.
auto Insert(size_type pos, InputIt first, InputIt last) -> std::iter_value_t< InputIt > *
Inserts elements from iterator range at the specified position.
const T * begin() const noexcept
Returns const iterator to the beginning.
constexpr TypedBufferArray(size_type count)
Constructs storage with count default-inserted elements of type T.
friend constexpr void swap(TypedBufferArray &lhs, TypedBufferArray &rhs) noexcept(std::is_nothrow_swappable_v< ContainerType >)
auto crend() const noexcept -> std::reverse_iterator< const T * >
Returns const reverse iterator before the first element.
void AppendRange(Range &&range)
Appends elements from a range to the end.
constexpr void Merge(const TypedBufferArray< OtherAllocator > &other)
TypedBufferArray(std::nullptr_t)=delete
std::vector< std::byte, allocator_type > ContainerType
constexpr TypedBufferArray(size_type count, const T &value)
Constructs storage with count copies of value.
constexpr TypedBufferArray(InputIt first, InputIt last)
Constructs storage from iterator range.
constexpr size_type Capacity() const noexcept
Returns the capacity in number of elements.
constexpr TypedBufferArray & operator=(TypedBufferArray &&other) noexcept(std::is_nothrow_move_assignable_v< ContainerType >)
constexpr TypedBufferArray(TypedBufferArray &&other) noexcept(std::is_nothrow_move_constructible_v< ContainerType >)
auto rend() noexcept -> std::reverse_iterator< T * >
Returns reverse iterator before the first element.
T * Insert(size_type pos, size_type count, const T &value)
Inserts count copies of value at the specified position.
constexpr TypedBufferArray(TypedBufferArray &&other, const allocator_type &alloc) noexcept(std::is_nothrow_constructible_v< ContainerType, const allocator_type & >)
constexpr bool IsType() const noexcept
Checks if the stored type matches T.
constexpr void PopBack() noexcept
Removes the last stored value.
constexpr TypedBufferArray & operator=(const TypedBufferArray &other)
const T * cbegin() const noexcept
Returns const iterator to the beginning.
constexpr void Swap(TypedBufferArray &other) noexcept(std::is_nothrow_swappable_v< ContainerType >)
constexpr TypedBufferArray & operator=(std::initializer_list< T > init)
Assignment from initializer list.
constexpr TypedBufferArray(std::pmr::memory_resource *resource) noexcept(std::is_nothrow_constructible_v< allocator_type, std::pmr::memory_resource * >)
Constructs an empty storage from a PMR memory resource.
constexpr TypedBufferArray(std::initializer_list< T > init)
Constructs storage from initializer list.
static constexpr TypeIndex From() noexcept
Constructs a TypeIndex from a type T.
constexpr auto erase(BasicStaticString< StrCapacity, CharT, Traits > &str, CharT value) noexcept -> typename BasicStaticString< StrCapacity, CharT, Traits >::size_type
Erases all elements equal to value.
TypedBufferArray< std::pmr::polymorphic_allocator< std::byte > > PmrTypedBufferArray
constexpr auto erase_if(BasicStaticString< StrCapacity, CharT, Traits > &str, Pred pred) noexcept -> typename BasicStaticString< StrCapacity, CharT, Traits >::size_type
Erases all elements satisfying predicate.
STL namespace.