docs
Loading...
Searching...
No Matches
Vector.h
Go to the documentation of this file.
1///
2/// Copyright (C) 2026 Ultralight, Inc. All rights reserved.
3/// A license is required for commercial use. https://ultralig.ht
4///
5#pragma once
6
8#include <Ultralight/platform/Allocator.h>
9#include <cassert>
10#include <cstring>
11#include <initializer_list>
12#include <iterator>
13#include <new>
14#include <type_traits>
15#include <utility>
16
17namespace ultralight {
18
19/// @cond INTERNAL
20namespace detail {
21
22template<typename T, size_t N>
23struct VectorStorage {
24 T* data_;
25 size_t size_ = 0;
26 size_t capacity_ = N;
27 alignas(T) unsigned char inline_storage_[sizeof(T) * N];
28
29 bool is_inline() const { return data_ == reinterpret_cast<const T*>(inline_storage_); }
30 T* inline_buffer() { return reinterpret_cast<T*>(inline_storage_); }
31 const T* inline_buffer() const { return reinterpret_cast<const T*>(inline_storage_); }
32
33 VectorStorage() : data_(reinterpret_cast<T*>(inline_storage_)) {}
34};
35
36template<typename T>
37struct VectorStorage<T, 0> {
38 T* data_ = nullptr;
39 size_t size_ = 0;
40 size_t capacity_ = 0;
41
42 bool is_inline() const { return false; }
43 T* inline_buffer() { return nullptr; }
44 const T* inline_buffer() const { return nullptr; }
45};
46
47} // namespace detail
48/// @endcond
49
50///
51/// @brief A std::vector-like container with small buffer optimization and ABI-safe allocation.
52///
53/// This container can be used safely in public API boundaries where `std::vector` cannot, because
54/// STL types are not guaranteed to be ABI-compatible across different compilers, standard library
55/// implementations, or even different build configurations.
56///
57/// All heap allocation is routed through UltralightCore's exported allocator functions
58/// (`ul_malloc` / `ul_free`), ensuring that memory allocated by the library is always freed by
59/// the library -- regardless of which runtime the caller was built with.
60///
61/// ## Small Buffer Optimization (SBO)
62///
63/// When `InlineCapacity > 0` (the default), the first N elements are stored inline within the
64/// Vector object itself, avoiding heap allocation for common small lists. Once the size exceeds
65/// `InlineCapacity`, the Vector transparently switches to heap storage.
66///
67/// ## Type support
68///
69/// Works with both trivially-copyable types (uses `memcpy`/`realloc` fast paths) and non-trivial
70/// types like `RefPtr<T>` (proper construction, destruction, and move semantics).
71///
72/// ## Example
73///
74/// ```
75/// Vector<int> v = {1, 2, 3}; // stored inline (default InlineCapacity = 4)
76/// v.push_back(4); // still inline
77/// v.push_back(5); // transitions to heap
78///
79/// Vector<int, 0> heap_only; // pure heap, no inline storage
80/// Vector<int, 16> large_inline; // 16 elements inline before heap
81/// ```
82///
83/// @tparam T Element type.
84/// @tparam InlineCapacity Number of elements stored inline before heap allocation (default 4).
85/// Use 0 for pure heap storage with no inline overhead.
86///
87template<typename T, size_t InlineCapacity = 4>
88class Vector : private detail::VectorStorage<T, InlineCapacity> {
89 using Storage = detail::VectorStorage<T, InlineCapacity>;
90 using Storage::data_;
91 using Storage::size_;
92 using Storage::capacity_;
93
94 template<typename U, size_t M>
95 friend class Vector;
96
97public:
98 using value_type = T;
99 using size_type = size_t;
100 using difference_type = ptrdiff_t;
101 using reference = T&;
102 using const_reference = const T&;
103 using pointer = T*;
104 using const_pointer = const T*;
105 using iterator = T*;
106 using const_iterator = const T*;
107
108 // ===== Constructors =====
109
110 ///
111 /// Construct an empty vector. No heap allocation is performed.
112 ///
113 Vector() = default;
114
115 ///
116 /// Construct a vector with @p count value-initialized elements.
117 ///
118 explicit Vector(size_t count) {
119 if (count > 0) {
120 ensure_capacity(count);
121 default_construct_range(data_, count);
122 size_ = count;
123 }
124 }
125
126 ///
127 /// Construct a vector with @p count copies of @p value.
128 ///
129 Vector(size_t count, const T& value) {
130 if (count > 0) {
131 ensure_capacity(count);
132 fill_construct_range(data_, count, value);
133 size_ = count;
134 }
135 }
136
137 Vector(std::initializer_list<T> init) {
138 if (init.size() > 0) {
139 ensure_capacity(init.size());
140 copy_construct_range(data_, init.begin(), init.size());
141 size_ = init.size();
142 }
143 }
144
145 template<typename InputIt,
146 typename = typename std::enable_if<!std::is_integral<InputIt>::value>::type>
147 Vector(InputIt first, InputIt last) {
148 size_t count = static_cast<size_t>(std::distance(first, last));
149 if (count > 0) {
150 ensure_capacity(count);
151 copy_construct_from_iter(data_, first, count);
152 size_ = count;
153 }
154 }
155
156 Vector(const Vector& other) {
157 if (other.size_ > 0) {
158 ensure_capacity(other.size_);
159 copy_construct_range(data_, other.data_, other.size_);
160 size_ = other.size_;
161 }
162 }
163
164 template<size_t OtherInline>
166 if (other.size_ > 0) {
167 ensure_capacity(other.size_);
168 copy_construct_range(data_, other.data_, other.size_);
169 size_ = other.size_;
170 }
171 }
172
173 Vector(Vector&& other)
174 noexcept(InlineCapacity == 0 || std::is_nothrow_move_constructible<T>::value) {
175 move_from(std::move(other));
176 }
177
178 template<size_t OtherInline>
180 noexcept(std::is_nothrow_move_constructible<T>::value) {
181 if (other.size_ > 0) {
182 ensure_capacity(other.size_);
183 move_construct_range(data_, other.data_, other.size_);
184 size_ = other.size_;
185 }
186 other.clear();
187 }
188
190 destroy_range(data_, size_);
191 free_heap();
192 }
193
194 // ===== Assignment =====
195
196 Vector& operator=(const Vector& other) {
197 if (this != &other)
198 assign_from_copy(other.data_, other.size_);
199 return *this;
200 }
201
202 template<size_t OtherInline>
204 assign_from_copy(other.data_, other.size_);
205 return *this;
206 }
207
209 noexcept(InlineCapacity == 0 || std::is_nothrow_move_constructible<T>::value) {
210 if (this != &other) {
211 destroy_range(data_, size_);
212 free_heap();
213 reset_to_inline();
214 move_from(std::move(other));
215 }
216 return *this;
217 }
218
219 template<size_t OtherInline>
221 noexcept(std::is_nothrow_move_constructible<T>::value) {
222 destroy_range(data_, size_);
223 size_ = 0;
224 if (other.size_ > 0) {
225 ensure_capacity(other.size_);
226 move_construct_range(data_, other.data_, other.size_);
227 size_ = other.size_;
228 }
229 other.clear();
230 return *this;
231 }
232
233 Vector& operator=(std::initializer_list<T> init) {
234 assign(init);
235 return *this;
236 }
237
238 void assign(size_t count, const T& value) {
239 destroy_range(data_, size_);
240 size_ = 0;
241 ensure_capacity(count);
242 fill_construct_range(data_, count, value);
243 size_ = count;
244 }
245
246 template<typename InputIt,
247 typename = typename std::enable_if<!std::is_integral<InputIt>::value>::type>
248 void assign(InputIt first, InputIt last) {
249 size_t count = static_cast<size_t>(std::distance(first, last));
250 destroy_range(data_, size_);
251 size_ = 0;
252 ensure_capacity(count);
253 copy_construct_from_iter(data_, first, count);
254 size_ = count;
255 }
256
257 void assign(std::initializer_list<T> init) {
258 destroy_range(data_, size_);
259 size_ = 0;
260 ensure_capacity(init.size());
261 copy_construct_range(data_, init.begin(), init.size());
262 size_ = init.size();
263 }
264
265 // ===== Element access =====
266
267 ///
268 /// Access element by index. No bounds checking.
269 ///
270 T& operator[](size_t index) { return data_[index]; }
271 const T& operator[](size_t index) const { return data_[index]; }
272
273 ///
274 /// Access element by index with bounds checking (asserts in debug builds).
275 ///
276 T& at(size_t index) { assert(index < size_); return data_[index]; }
277 const T& at(size_t index) const { assert(index < size_); return data_[index]; }
278
279 T& front() { assert(size_ > 0); return data_[0]; }
280 const T& front() const { assert(size_ > 0); return data_[0]; }
281
282 T& back() { assert(size_ > 0); return data_[size_ - 1]; }
283 const T& back() const { assert(size_ > 0); return data_[size_ - 1]; }
284
285 T* data() { return data_; }
286 const T* data() const { return data_; }
287
288 // ===== Iterators =====
289
290 iterator begin() { return data_; }
291 const_iterator begin() const { return data_; }
292 const_iterator cbegin() const { return data_; }
293
294 iterator end() { return data_ + size_; }
295 const_iterator end() const { return data_ + size_; }
296 const_iterator cend() const { return data_ + size_; }
297
298 // ===== Capacity =====
299
300 bool empty() const { return size_ == 0; }
301 size_t size() const { return size_; }
302
303 ///
304 /// Returns the number of elements that can be held without allocating. For a default-constructed
305 /// vector with `InlineCapacity > 0`, this equals `InlineCapacity` (inline storage is always
306 /// available without heap allocation).
307 ///
308 size_t capacity() const { return capacity_; }
309
310 ///
311 /// Ensure capacity for at least @p min_capacity elements, allocating if necessary.
312 ///
313 void reserve(size_t min_capacity) {
314 if (min_capacity > capacity_)
315 grow_to(min_capacity);
316 }
317
318 ///
319 /// Reduce capacity to fit the current size. If `InlineCapacity > 0` and the current size fits
320 /// within the inline buffer, storage is moved back to inline (freeing the heap allocation).
321 ///
323 if (size_ == capacity_)
324 return;
325
326 if (size_ == 0) {
327 free_heap();
328 reset_to_inline();
329 return;
330 }
331
332 if constexpr (InlineCapacity > 0) {
333 if (size_ <= InlineCapacity && !this->is_inline()) {
334 T* old_data = data_;
335 size_t old_size = size_;
336 data_ = this->inline_buffer();
337 capacity_ = InlineCapacity;
338 move_construct_range(data_, old_data, old_size);
339 destroy_range(old_data, old_size);
340 deallocate(old_data);
341 return;
342 }
343 }
344
345 if (!this->is_inline()) {
346 if constexpr (std::is_trivially_copyable_v<T>) {
347 void* p = reallocate(data_, size_ * sizeof(T));
348 data_ = static_cast<T*>(p);
349 capacity_ = size_;
350 } else {
351 T* new_data = static_cast<T*>(allocate(size_ * sizeof(T)));
352 move_construct_range(new_data, data_, size_);
353 destroy_range(data_, size_);
354 deallocate(data_);
355 data_ = new_data;
356 capacity_ = size_;
357 }
358 }
359 }
360
361 // ===== Modifiers =====
362
363 void clear() {
364 destroy_range(data_, size_);
365 size_ = 0;
366 }
367
368 void push_back(const T& value) {
369 if (UL_LIKELY(size_ < capacity_)) {
370 construct_at(data_ + size_, value);
371 ++size_;
372 return;
373 }
374 // Value may reference internal storage -- copy before grow
375 T tmp(value);
376 grow();
377 construct_at(data_ + size_, std::move(tmp));
378 ++size_;
379 }
380
381 void push_back(T&& value) {
382 if (UL_LIKELY(size_ < capacity_)) {
383 construct_at(data_ + size_, std::move(value));
384 ++size_;
385 return;
386 }
387 T tmp(std::move(value));
388 grow();
389 construct_at(data_ + size_, std::move(tmp));
390 ++size_;
391 }
392
393 ///
394 /// Construct an element in-place at the end of the vector.
395 ///
396 template<typename... Args>
397 T& emplace_back(Args&&... args) {
398 if (UL_LIKELY(size_ < capacity_)) {
399 construct_at(data_ + size_, std::forward<Args>(args)...);
400 return data_[size_++];
401 }
402 return emplace_back_slow(std::forward<Args>(args)...);
403 }
404
405 ///
406 /// Append an element without checking capacity. Caller must ensure `size() < capacity()`.
407 ///
408 void unchecked_append(const T& value) {
409 assert(size_ < capacity_);
410 construct_at(data_ + size_, value);
411 ++size_;
412 }
413
414 ///
415 /// Construct an element in-place at the end without checking capacity.
416 /// Caller must ensure `size() < capacity()`.
417 ///
418 template<typename... Args>
419 T& unchecked_emplace_back(Args&&... args) {
420 assert(size_ < capacity_);
421 construct_at(data_ + size_, std::forward<Args>(args)...);
422 return data_[size_++];
423 }
424
425 void pop_back() {
426 assert(size_ > 0);
427 --size_;
428 destroy_at(data_ + size_);
429 }
430
431 ///
432 /// Remove the last element and return it by move.
433 ///
435 assert(size_ > 0);
436 --size_;
437 T result(std::move(data_[size_]));
438 destroy_at(data_ + size_);
439 return result;
440 }
441
442 iterator insert(const_iterator pos, const T& value) {
443 size_t index = static_cast<size_t>(pos - data_);
444 assert(index <= size_);
445 if (UL_UNLIKELY(size_ == capacity_))
446 return insert_slow_path(index, value);
447 T tmp(value);
448 insert_no_grow(index, std::move(tmp));
449 return data_ + index;
450 }
451
453 size_t index = static_cast<size_t>(pos - data_);
454 assert(index <= size_);
455 if (UL_UNLIKELY(size_ == capacity_))
456 return insert_slow_path(index, std::move(value));
457 T tmp(std::move(value));
458 insert_no_grow(index, std::move(tmp));
459 return data_ + index;
460 }
461
462 template<typename... Args>
463 iterator emplace(const_iterator pos, Args&&... args) {
464 size_t index = static_cast<size_t>(pos - data_);
465 assert(index <= size_);
466 if (UL_UNLIKELY(size_ == capacity_))
467 return insert_slow_path(index, std::forward<Args>(args)...);
468 if constexpr (std::is_trivially_copyable_v<T>) {
469 std::memmove(data_ + index + 1, data_ + index, (size_ - index) * sizeof(T));
470 construct_at(data_ + index, std::forward<Args>(args)...);
471 } else if (index == size_) {
472 construct_at(data_ + index, std::forward<Args>(args)...);
473 } else {
474 construct_at(data_ + size_, std::move(data_[size_ - 1]));
475 for (size_t i = size_ - 1; i > index; --i)
476 data_[i] = std::move(data_[i - 1]);
477 destroy_at(data_ + index);
478 construct_at(data_ + index, std::forward<Args>(args)...);
479 }
480 ++size_;
481 return data_ + index;
482 }
483
485 size_t index = static_cast<size_t>(pos - data_);
486 assert(index < size_);
487 if constexpr (std::is_trivially_copyable_v<T>) {
488 std::memmove(data_ + index, data_ + index + 1, (size_ - index - 1) * sizeof(T));
489 } else {
490 for (size_t i = index; i < size_ - 1; ++i)
491 data_[i] = std::move(data_[i + 1]);
492 destroy_at(data_ + size_ - 1);
493 }
494 --size_;
495 return data_ + index;
496 }
497
499 if (first == last)
500 return const_cast<iterator>(first);
501 size_t start = static_cast<size_t>(first - data_);
502 size_t count = static_cast<size_t>(last - first);
503 assert(start + count <= size_);
504 size_t remaining = size_ - start - count;
505 if constexpr (std::is_trivially_copyable_v<T>) {
506 std::memmove(data_ + start, data_ + start + count, remaining * sizeof(T));
507 } else {
508 for (size_t i = 0; i < remaining; ++i)
509 data_[start + i] = std::move(data_[start + count + i]);
510 destroy_range(data_ + size_ - count, count);
511 }
512 size_ -= count;
513 return data_ + start;
514 }
515
516 void resize(size_t count) {
517 if (count < size_) {
518 destroy_range(data_ + count, size_ - count);
519 size_ = count;
520 } else if (count > size_) {
521 ensure_capacity(count);
522 default_construct_range(data_ + size_, count - size_);
523 size_ = count;
524 }
525 }
526
527 void resize(size_t count, const T& value) {
528 if (count < size_) {
529 destroy_range(data_ + count, size_ - count);
530 size_ = count;
531 } else if (count > size_) {
532 ensure_capacity(count);
533 fill_construct_range(data_ + size_, count - size_, value);
534 size_ = count;
535 }
536 }
537
538 void swap(Vector& other) {
539 if constexpr (InlineCapacity == 0) {
540 std::swap(data_, other.data_);
541 std::swap(size_, other.size_);
542 std::swap(capacity_, other.capacity_);
543 } else {
544 bool a_inline = this->is_inline();
545 bool b_inline = other.is_inline();
546
547 if (!a_inline && !b_inline) {
548 std::swap(data_, other.data_);
549 std::swap(size_, other.size_);
550 std::swap(capacity_, other.capacity_);
551 } else if (a_inline && b_inline) {
552 swap_both_inline(other);
553 } else if (a_inline) {
554 swap_inline_with_heap(other);
555 } else {
556 other.swap_inline_with_heap(*this);
557 }
558 }
559 }
560
561 // ===== Bulk operations =====
562
563 ///
564 /// Append all elements from another vector. Works across different InlineCapacity values.
565 ///
566 template<size_t OtherInline>
568 if (other.size_ == 0)
569 return;
570 ensure_capacity(size_ + other.size_);
571 copy_construct_range(data_ + size_, other.data_, other.size_);
572 size_ += other.size_;
573 }
574
575 ///
576 /// Move-append all elements from another vector, leaving it empty.
577 ///
578 template<size_t OtherInline>
580 if (other.size_ == 0)
581 return;
582 ensure_capacity(size_ + other.size_);
583 move_construct_range(data_ + size_, other.data_, other.size_);
584 size_ += other.size_;
585 other.clear();
586 }
587
588 // ===== Comparison =====
589
590 friend bool operator==(const Vector& a, const Vector& b) {
591 if (a.size_ != b.size_)
592 return false;
593 if constexpr (std::is_trivially_copyable_v<T>) {
594 return std::memcmp(a.data_, b.data_, a.size_ * sizeof(T)) == 0;
595 } else {
596 for (size_t i = 0; i < a.size_; ++i) {
597 if (!(a.data_[i] == b.data_[i]))
598 return false;
599 }
600 return true;
601 }
602 }
603
604 friend bool operator!=(const Vector& a, const Vector& b) { return !(a == b); }
605
606private:
607 // ===== Allocation helpers =====
608
609 static void* allocate(size_t bytes) {
610 void* p;
611 if constexpr (alignof(T) > alignof(std::max_align_t))
612 p = ul_aligned_malloc(bytes, alignof(T));
613 else
614 p = ul_malloc(bytes);
615 assert(p && "Vector: allocation failed");
616 return p;
617 }
618
619 static void deallocate(void* ptr) {
620 if constexpr (alignof(T) > alignof(std::max_align_t))
621 ul_aligned_free(ptr);
622 else
623 ul_free(ptr);
624 }
625
626 static void* reallocate(void* ptr, size_t bytes) {
627 void* p;
628 if constexpr (alignof(T) > alignof(std::max_align_t))
629 p = ul_aligned_realloc(ptr, bytes, alignof(T));
630 else
631 p = ul_realloc(ptr, bytes);
632 assert(p && "Vector: reallocation failed");
633 return p;
634 }
635
636 // ===== Construction / destruction helpers =====
637
638 template<typename... Args>
639 static void construct_at(T* ptr, Args&&... args) {
640 ::new (static_cast<void*>(ptr)) T(std::forward<Args>(args)...);
641 }
642
643 static void destroy_at(T* ptr) {
644 if constexpr (!std::is_trivially_destructible_v<T>)
645 ptr->~T();
646 }
647
648 static void destroy_range(T* ptr, size_t count) {
649 if constexpr (!std::is_trivially_destructible_v<T>) {
650 for (size_t i = 0; i < count; ++i)
651 ptr[i].~T();
652 }
653 }
654
655 static void copy_construct_range(T* dst, const T* src, size_t count) {
656 if constexpr (std::is_trivially_copyable_v<T>) {
657 std::memcpy(dst, src, count * sizeof(T));
658 } else {
659 for (size_t i = 0; i < count; ++i)
660 ::new (static_cast<void*>(dst + i)) T(src[i]);
661 }
662 }
663
664 static void move_construct_range(T* dst, T* src, size_t count) {
665 if constexpr (std::is_trivially_copyable_v<T>) {
666 std::memcpy(dst, src, count * sizeof(T));
667 } else {
668 for (size_t i = 0; i < count; ++i)
669 ::new (static_cast<void*>(dst + i)) T(std::move(src[i]));
670 }
671 }
672
673 static void fill_construct_range(T* dst, size_t count, const T& value) {
674 for (size_t i = 0; i < count; ++i)
675 ::new (static_cast<void*>(dst + i)) T(value);
676 }
677
678 static void default_construct_range(T* dst, size_t count) {
679 if constexpr (std::is_trivially_default_constructible_v<T>) {
680 std::memset(dst, 0, count * sizeof(T));
681 } else {
682 for (size_t i = 0; i < count; ++i)
683 ::new (static_cast<void*>(dst + i)) T();
684 }
685 }
686
687 template<typename InputIt>
688 static void copy_construct_from_iter(T* dst, InputIt first, size_t count) {
689 for (size_t i = 0; i < count; ++i, ++first)
690 ::new (static_cast<void*>(dst + i)) T(*first);
691 }
692
693 /// Move-construct elements to a new non-overlapping buffer and destroy originals.
694 static void relocate_range(T* dst, T* src, size_t count) {
695 if (count == 0)
696 return;
697 if constexpr (std::is_trivially_copyable_v<T>) {
698 std::memcpy(dst, src, count * sizeof(T));
699 } else {
700 for (size_t i = 0; i < count; ++i) {
701 ::new (static_cast<void*>(dst + i)) T(std::move(src[i]));
702 src[i].~T();
703 }
704 }
705 }
706
707 // ===== Capacity helpers =====
708
709 size_t next_capacity() const {
710 size_t new_cap = capacity_ + capacity_ / 2 + 1;
711 assert(new_cap > capacity_ && "Vector: capacity overflow");
712 return new_cap;
713 }
714
715 void grow() { grow_to(next_capacity()); }
716
717 void grow_to(size_t new_cap) {
718 assert(new_cap > capacity_);
719 assert(new_cap <= SIZE_MAX / sizeof(T) && "Vector: allocation size overflow");
720
721 if constexpr (std::is_trivially_copyable_v<T>) {
722 if (!this->is_inline()) {
723 data_ = static_cast<T*>(reallocate(data_, new_cap * sizeof(T)));
724 } else {
725 T* new_data = static_cast<T*>(allocate(new_cap * sizeof(T)));
726 std::memcpy(new_data, data_, size_ * sizeof(T));
727 data_ = new_data;
728 }
729 } else {
730 T* new_data = static_cast<T*>(allocate(new_cap * sizeof(T)));
731 for (size_t i = 0; i < size_; ++i) {
732 ::new (static_cast<void*>(new_data + i)) T(std::move(data_[i]));
733 data_[i].~T();
734 }
735 if (!this->is_inline())
736 deallocate(data_);
737 data_ = new_data;
738 }
739 capacity_ = new_cap;
740 }
741
742 void ensure_capacity(size_t min_cap) {
743 if (min_cap > capacity_)
744 grow_to(min_cap);
745 }
746
747 // ===== Buffer management =====
748
749 void free_heap() {
750 if (!this->is_inline() && data_)
751 deallocate(data_);
752 }
753
754 void reset_to_inline() {
755 if constexpr (InlineCapacity > 0) {
756 data_ = this->inline_buffer();
757 capacity_ = InlineCapacity;
758 } else {
759 data_ = nullptr;
760 capacity_ = 0;
761 }
762 size_ = 0;
763 }
764
765 void move_from(Vector&& other) {
766 if constexpr (InlineCapacity == 0) {
767 data_ = other.data_;
768 size_ = other.size_;
769 capacity_ = other.capacity_;
770 other.data_ = nullptr;
771 other.size_ = 0;
772 other.capacity_ = 0;
773 } else {
774 if (other.is_inline()) {
775 if constexpr (std::is_trivially_copyable_v<T>) {
776 std::memcpy(data_, other.data_, other.size_ * sizeof(T));
777 } else {
778 move_construct_range(data_, other.data_, other.size_);
779 destroy_range(other.data_, other.size_);
780 }
781 size_ = other.size_;
782 other.size_ = 0;
783 } else {
784 // Steal heap buffer
785 data_ = other.data_;
786 size_ = other.size_;
787 capacity_ = other.capacity_;
788 other.data_ = other.inline_buffer();
789 other.size_ = 0;
790 other.capacity_ = InlineCapacity;
791 }
792 }
793 }
794
795 void assign_from_copy(const T* src, size_t count) {
796 destroy_range(data_, size_);
797 size_ = 0;
798 ensure_capacity(count);
799 copy_construct_range(data_, src, count);
800 size_ = count;
801 }
802
803 // ===== Insert / erase helpers =====
804
805 void insert_no_grow(size_t index, T&& val) {
806 if constexpr (std::is_trivially_copyable_v<T>) {
807 std::memmove(data_ + index + 1, data_ + index, (size_ - index) * sizeof(T));
808 std::memcpy(data_ + index, &val, sizeof(T));
809 } else if (index == size_) {
810 construct_at(data_ + index, std::move(val));
811 } else {
812 construct_at(data_ + size_, std::move(data_[size_ - 1]));
813 for (size_t i = size_ - 1; i > index; --i)
814 data_[i] = std::move(data_[i - 1]);
815 data_[index] = std::move(val);
816 }
817 ++size_;
818 }
819
820 /// Grow + insert: allocate new buffer, construct element at index, relocate rest around it.
821 /// Safe when args reference internal storage (old buffer is valid during construct).
822 template<typename... Args>
823 iterator insert_slow_path(size_t index, Args&&... args) {
824 size_t new_cap = next_capacity();
825 T* new_data = static_cast<T*>(allocate(new_cap * sizeof(T)));
826 // Construct new element first while old buffer is still valid
827 construct_at(new_data + index, std::forward<Args>(args)...);
828 // Relocate existing elements around it
829 relocate_range(new_data, data_, index);
830 relocate_range(new_data + index + 1, data_ + index, size_ - index);
831 if (!this->is_inline())
832 deallocate(data_);
833 data_ = new_data;
834 capacity_ = new_cap;
835 ++size_;
836 return data_ + index;
837 }
838
839 template<typename... Args>
840 T& emplace_back_slow(Args&&... args) {
841 size_t new_cap = next_capacity();
842 T* new_data = static_cast<T*>(allocate(new_cap * sizeof(T)));
843 construct_at(new_data + size_, std::forward<Args>(args)...);
844 relocate_range(new_data, data_, size_);
845 if (!this->is_inline())
846 deallocate(data_);
847 data_ = new_data;
848 capacity_ = new_cap;
849 return data_[size_++];
850 }
851
852 // ===== Swap helpers =====
853
854 void swap_both_inline(Vector& other) {
855 size_t a_size = size_;
856 size_t b_size = other.size_;
857
858 if constexpr (std::is_trivially_copyable_v<T>) {
859 alignas(T) unsigned char tmp[sizeof(T) * InlineCapacity];
860 std::memcpy(tmp, data_, a_size * sizeof(T));
861 std::memcpy(data_, other.data_, b_size * sizeof(T));
862 std::memcpy(other.data_, tmp, a_size * sizeof(T));
863 } else {
864 size_t min_size = a_size < b_size ? a_size : b_size;
865 for (size_t i = 0; i < min_size; ++i) {
866 using std::swap;
867 swap(data_[i], other.data_[i]);
868 }
869 if (a_size > b_size) {
870 move_construct_range(other.data_ + min_size, data_ + min_size, a_size - b_size);
871 destroy_range(data_ + min_size, a_size - b_size);
872 } else if (b_size > a_size) {
873 move_construct_range(data_ + min_size, other.data_ + min_size, b_size - a_size);
874 destroy_range(other.data_ + min_size, b_size - a_size);
875 }
876 }
877
878 size_ = b_size;
879 other.size_ = a_size;
880 }
881
882 /// this is inline, other is heap. Swap storage.
883 void swap_inline_with_heap(Vector& other) {
884 T* heap_data = other.data_;
885 size_t heap_size = other.size_;
886 size_t heap_cap = other.capacity_;
887
888 // Move our inline elements to other's inline buffer
889 other.data_ = other.inline_buffer();
890 other.capacity_ = InlineCapacity;
891 other.size_ = 0;
892
893 if constexpr (std::is_trivially_copyable_v<T>) {
894 std::memcpy(other.data_, data_, size_ * sizeof(T));
895 } else {
896 move_construct_range(other.data_, data_, size_);
897 destroy_range(data_, size_);
898 }
899 other.size_ = size_;
900
901 // Steal the heap buffer
902 data_ = heap_data;
903 size_ = heap_size;
904 capacity_ = heap_cap;
905 }
906};
907
908template<typename T, size_t N>
909void swap(Vector<T, N>& a, Vector<T, N>& b) { a.swap(b); }
910
911} // namespace ultralight
#define UL_LIKELY(x)
Definition Defines.h:84
#define UL_UNLIKELY(x)
Definition Defines.h:76
A std::vector-like container with small buffer optimization and ABI-safe allocation.
Definition Vector.h:88
void pop_back()
Definition Vector.h:425
Vector & operator=(Vector< T, OtherInline > &&other) noexcept(std::is_nothrow_move_constructible< T >::value)
Definition Vector.h:220
T & reference
Definition Vector.h:101
T & operator[](size_t index)
Access element by index.
Definition Vector.h:270
const T & at(size_t index) const
Definition Vector.h:277
void swap(Vector &other)
Definition Vector.h:538
size_t size() const
Definition Vector.h:301
const T * const_iterator
Definition Vector.h:106
const_iterator begin() const
Definition Vector.h:291
iterator erase(const_iterator pos)
Definition Vector.h:484
Vector & operator=(std::initializer_list< T > init)
Definition Vector.h:233
const T & front() const
Definition Vector.h:280
iterator insert(const_iterator pos, const T &value)
Definition Vector.h:442
const_iterator cbegin() const
Definition Vector.h:292
T & emplace_back(Args &&... args)
Construct an element in-place at the end of the vector.
Definition Vector.h:397
friend bool operator==(const Vector &a, const Vector &b)
Definition Vector.h:590
void assign(size_t count, const T &value)
Definition Vector.h:238
void unchecked_append(const T &value)
Append an element without checking capacity.
Definition Vector.h:408
T & back()
Definition Vector.h:282
const T * data() const
Definition Vector.h:286
void append_vector(const Vector< T, OtherInline > &other)
Append all elements from another vector.
Definition Vector.h:567
T * iterator
Definition Vector.h:105
void resize(size_t count)
Definition Vector.h:516
void assign(std::initializer_list< T > init)
Definition Vector.h:257
iterator emplace(const_iterator pos, Args &&... args)
Definition Vector.h:463
void shrink_to_fit()
Reduce capacity to fit the current size.
Definition Vector.h:322
iterator insert(const_iterator pos, T &&value)
Definition Vector.h:452
bool empty() const
Definition Vector.h:300
void push_back(const T &value)
Definition Vector.h:368
void assign(InputIt first, InputIt last)
Definition Vector.h:248
size_t capacity() const
Returns the number of elements that can be held without allocating.
Definition Vector.h:308
Vector & operator=(const Vector< T, OtherInline > &other)
Definition Vector.h:203
size_t size_type
Definition Vector.h:99
T & at(size_t index)
Access element by index with bounds checking (asserts in debug builds).
Definition Vector.h:276
const T & back() const
Definition Vector.h:283
const_iterator cend() const
Definition Vector.h:296
Vector(size_t count, const T &value)
Construct a vector with count copies of value.
Definition Vector.h:129
Vector(const Vector< T, OtherInline > &other)
Definition Vector.h:165
Vector(InputIt first, InputIt last)
Definition Vector.h:147
Vector(Vector< T, OtherInline > &&other) noexcept(std::is_nothrow_move_constructible< T >::value)
Definition Vector.h:179
iterator erase(const_iterator first, const_iterator last)
Definition Vector.h:498
T take_last()
Remove the last element and return it by move.
Definition Vector.h:434
T & front()
Definition Vector.h:279
Vector & operator=(Vector &&other) noexcept(InlineCapacity==0||std::is_nothrow_move_constructible< T >::value)
Definition Vector.h:208
~Vector()
Definition Vector.h:189
void push_back(T &&value)
Definition Vector.h:381
T * pointer
Definition Vector.h:103
Vector(std::initializer_list< T > init)
Definition Vector.h:137
Vector()=default
Construct an empty vector.
friend class Vector
Definition Vector.h:95
void resize(size_t count, const T &value)
Definition Vector.h:527
void clear()
Definition Vector.h:363
const T & const_reference
Definition Vector.h:102
void append_vector(Vector< T, OtherInline > &&other)
Move-append all elements from another vector, leaving it empty.
Definition Vector.h:579
iterator end()
Definition Vector.h:294
T & unchecked_emplace_back(Args &&... args)
Construct an element in-place at the end without checking capacity.
Definition Vector.h:419
const_iterator end() const
Definition Vector.h:295
void reserve(size_t min_capacity)
Ensure capacity for at least min_capacity elements, allocating if necessary.
Definition Vector.h:313
iterator begin()
Definition Vector.h:290
T * data()
Definition Vector.h:285
Vector(const Vector &other)
Definition Vector.h:156
friend bool operator!=(const Vector &a, const Vector &b)
Definition Vector.h:604
const T * const_pointer
Definition Vector.h:104
ptrdiff_t difference_type
Definition Vector.h:100
Vector(Vector &&other) noexcept(InlineCapacity==0||std::is_nothrow_move_constructible< T >::value)
Definition Vector.h:173
Vector(size_t count)
Construct a vector with count value-initialized elements.
Definition Vector.h:118
Vector & operator=(const Vector &other)
Definition Vector.h:196
T value_type
Definition Vector.h:98
const T & operator[](size_t index) const
Definition Vector.h:271
Root namespace for every public Ultralight type, function, and enumeration.
void swap(RefPtr< T > &a, RefPtr< T > &b)
Definition RefPtr.h:341