3 * @brief Vector template implementation.
5 * Contains the implementation of VectorPro::Vector template member
6 * functions and internal implementation details.
9// ============================================================
11// Template implementation for VectorPro::Vector.
12// ============================================================
15// 1. Constructors & Destructor
16// 2. Copy & Move Semantics
27// 13. Non-member Functions
29// ============================================================
33// ============================================================
34// Section 1 — Constructors & Destructor
35// ============================================================
37template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
39 requires std::destructible<T>
40Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::Vector(std::size_t count,
46 data_ = std::allocator_traits<Allocator>::allocate(alloc_, count);
49 if constexpr (std::is_trivially_copyable_v<T> && std::is_same_v<Allocator, std::allocator<T>>) {
50 // std::fill_n on a trivial type vectorizes (or reduces to memset for
51 // zero-fill) far better than a per-element placement-new loop does.
52 std::fill_n(data_, count, value);
54 for (std::size_t i = 0; i < count; ++i) {
55 std::allocator_traits<Allocator>::construct(alloc_, data_ + i, value);
62template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
64 requires std::destructible<T>
65Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::Vector(std::initializer_list<T> init) {
66 if (init.size() == 0) {
70 data_ = std::allocator_traits<Allocator>::allocate(alloc_, init.size());
73 for (const auto& val : init) {
74 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_++, val);
78template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
80 requires std::destructible<T>
81template <std::input_iterator It>
82Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::Vector(It first, It last) {
83 for (; first != last; ++first) {
88template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
90 requires std::destructible<T>
91Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::Vector(const Allocator& alloc)
94template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
96 requires std::destructible<T>
97Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::~Vector() noexcept {
100 if constexpr (EnableEvents) {
101 if (listenerStore_.listeners_) {
102 delete[] listenerStore_.listeners_;
103 listenerStore_.listeners_ = nullptr;
108// ============================================================
109// Section 2 — Copy & Move Semantics
110// ============================================================
112template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
114 requires std::destructible<T>
115Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::Vector(const Vector& other)
117 std::allocator_traits<Allocator>::select_on_container_copy_construction(other.alloc_)) {
118 copyBufferFrom(other);
121template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
123 requires std::destructible<T>
124// Complexity here is inherent to correctly handling allocator propagation,
125// the in-capacity fast/slow paths, and exception safety together in one
126// place. Splitting it purely to satisfy this metric risks introducing a
127// real bug in exception-sensitive code for no correctness or readability
128// gain, so it's intentionally suppressed rather than refactored.
129Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>&
130// NOLINTNEXTLINE(readability-function-cognitive-complexity)
131Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::operator=(const Vector& other) {
132 if (this == &other) {
136 constexpr bool kPropagate =
137 std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value;
139 if constexpr (kPropagate) {
140 if (!(alloc_ == other.alloc_)) {
141 // Our current storage was obtained from the old allocator instance;
142 // it must be released before adopting the new one, since the new
143 // allocator may not be able to deallocate memory it didn't allocate.
145 alloc_ = other.alloc_;
149 if (other.vsize_ <= vcap_) {
150 // Fast path: existing capacity is sufficient, no allocation needed.
151 if constexpr (std::is_trivially_copyable_v<T> &&
152 std::is_same_v<Allocator, std::allocator<T>>) {
153 if (other.vsize_ > 0) {
154 std::memcpy(std::to_address(data_), std::to_address(other.data_),
155 other.vsize_ * sizeof(T));
157 for (std::size_t i = other.vsize_; i < vsize_; ++i) {
158 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
161 std::size_t common = std::min(vsize_, other.vsize_);
163 for (std::size_t i = 0; i < common; ++i) {
164 data_[i] = other.data_[i];
167 if (other.vsize_ > vsize_) {
168 std::size_t constructed = vsize_;
170 for (; constructed < other.vsize_; ++constructed) {
171 std::allocator_traits<Allocator>::construct(alloc_, data_ + constructed,
172 other.data_[constructed]);
175 for (std::size_t i = vsize_; i < constructed; ++i) {
176 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
178 throw; // basic guarantee: vsize_ left at its pre-call value
181 for (std::size_t i = other.vsize_; i < vsize_; ++i) {
182 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
187 vsize_ = other.vsize_;
189 // Not enough capacity — fall back to copy-and-swap for the strong guarantee.
190 // tmp's allocator must itself respect propagate_on_container_copy_assignment:
191 // only adopt other's allocator (via select_on_container_copy_construction)
192 // when propagation is permitted; otherwise keep using our own, so that
193 // after the swap, *this still owns the allocator it started with.
195 tmp.alloc_ = kPropagate
196 ? std::allocator_traits<Allocator>::select_on_container_copy_construction(
199 tmp.copyBufferFrom(other);
206template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
208 requires std::destructible<T>
209Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::Vector(Vector&& other) noexcept
210 : alloc_(std::move(other.alloc_)), data_(other.data_), vsize_(other.vsize_), vcap_(other.vcap_),
211 listenerStore_(other.listenerStore_) {
212 other.data_ = nullptr;
215 if constexpr (EnableEvents) {
216 other.listenerStore_.listeners_ = nullptr;
217 other.listenerStore_.lsize_ = 0;
218 other.listenerStore_.lcap_ = 0;
222template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
224 requires std::destructible<T>
225// See the same note above operator=(const Vector&) -- the canSteal /
226// element-move branches here are exception-sensitive and intentionally
227// kept together rather than split purely to satisfy this metric.
228Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>&
229// NOLINTNEXTLINE(readability-function-cognitive-complexity)
230Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::operator=(Vector&& other) noexcept {
231 if (this != &other) {
232 constexpr bool kPropagate =
233 std::allocator_traits<Allocator>::propagate_on_container_move_assignment::value;
234 constexpr bool kAlwaysEqual = std::allocator_traits<Allocator>::is_always_equal::value;
236 const bool canSteal = kPropagate || kAlwaysEqual || (alloc_ == other.alloc_);
239 if constexpr (EnableEvents) {
240 if (listenerStore_.listeners_) {
241 delete[] listenerStore_.listeners_;
242 listenerStore_.listeners_ = nullptr;
247 if constexpr (kPropagate) {
248 alloc_ = std::move(other.alloc_);
252 vsize_ = other.vsize_;
254 if constexpr (EnableEvents) {
255 listenerStore_ = other.listenerStore_;
258 other.data_ = nullptr;
261 if constexpr (EnableEvents) {
262 other.listenerStore_.listeners_ = nullptr;
263 other.listenerStore_.lsize_ = 0;
264 other.listenerStore_.lcap_ = 0;
267 // Unequal, non-propagating allocators: move elements individually
268 // using our own allocator; leave `other` cleared via its own release().
269 if (other.vsize_ > 0) {
270 data_ = std::allocator_traits<Allocator>::allocate(alloc_, other.vsize_);
271 vcap_ = other.vsize_;
272 for (std::size_t i = 0; i < other.vsize_; ++i) {
273 std::allocator_traits<Allocator>::construct(alloc_, data_ + i,
274 std::move(other.data_[i]));
276 vsize_ = other.vsize_;
285// ============================================================
286// Section 3 — Modifiers
287// ============================================================
289template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
291 requires std::destructible<T>
292void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::push_back(const T& value) {
293 // Hot path only: no branch-heavy aliasing bookkeeping, no reallocate()
294 // call site inline here. Small enough to reliably inline at call sites,
295 // which the mixed hot/cold version wasn't.
296 if (vsize_ == vcap_) [[unlikely]] {
297 grow_and_push_back(value);
300 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_, value);
301 std::size_t old = vsize_;
303 notify({EventType::PUSHBACK, old, old, vsize_});
306template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
308 requires std::destructible<T>
309void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::grow_and_push_back(const T& value) {
310 // About to reallocate, which can invalidate `value` if it's a
311 // reference into our own storage (e.g. v.push_back(v[i])).
312 // Snapshot its offset now, while the old buffer is still valid.
313 std::ptrdiff_t offset = -1;
314 if (data_ && std::addressof(value) >= data_ && std::addressof(value) < data_ + vsize_) {
315 offset = std::addressof(value) - data_;
318 reallocate(growCapacity());
320 const T& src = (offset >= 0) ? data_[offset] : value;
321 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_, src);
323 std::size_t old = vsize_;
325 notify({EventType::PUSHBACK, old, old, vsize_});
328template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
330 requires std::destructible<T>
331void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::push_back(T&& value) {
332 if (vsize_ == vcap_) [[unlikely]] {
333 grow_and_push_back(std::move(value));
336 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_, std::move(value));
337 std::size_t old = vsize_;
339 notify({EventType::PUSHBACK, old, old, vsize_});
342template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
344 requires std::destructible<T>
345void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::grow_and_push_back(T&& value) {
346 // About to reallocate, which can invalidate `value` if it's a
347 // reference into our own storage (e.g. v.push_back(std::move(v[i]))).
348 // Snapshot its offset now, while the old buffer is still valid.
349 std::ptrdiff_t offset = -1;
350 if (data_ && std::addressof(value) >= data_ && std::addressof(value) < data_ + vsize_) {
351 offset = std::addressof(value) - data_;
354 reallocate(growCapacity());
357 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_,
358 std::move(data_[offset]));
360 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_, std::move(value));
363 std::size_t old = vsize_;
365 notify({EventType::PUSHBACK, old, old, vsize_});
368template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
370 requires std::destructible<T>
371template <typename... Args>
372 requires std::constructible_from<T, Args...>
373void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::emplace_back(Args&&... args) {
374 if (vsize_ == vcap_) {
375 reallocate(growCapacity());
378 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_,
379 std::forward<Args>(args)...);
381 std::size_t old = vsize_;
383 notify({EventType::EMPLACEBACK, old, old, vsize_});
386template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
388 requires std::destructible<T>
389typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::iterator
390Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::insert(const_iterator pos,
392 std::size_t index = pos - cbegin();
393 const bool willReallocate = (vsize_ == vcap_);
394 const bool willShift = (index != vsize_);
396 if (!willReallocate && !willShift) {
397 // Fast path: plain append with capacity to spare. Nothing below
398 // touches or invalidates `value`, so no aliasing check is needed.
399 std::allocator_traits<Allocator>::construct(alloc_, data_ + index, value);
401 // Either reallocation (invalidates the old buffer) or the shift
402 // loop (can overwrite the slot `value` points to) is about to
403 // happen — snapshot `value` first if it aliases our own storage.
404 std::ptrdiff_t offset = -1;
405 if (data_ && std::addressof(value) >= data_ && std::addressof(value) < data_ + vsize_) {
406 offset = std::addressof(value) - data_;
409 if (willReallocate) {
410 reallocate(growCapacity());
413 // Snapshot before shifting — the shift loop can overwrite `offset`
414 // before we'd otherwise read it.
415 std::optional<T> snapshot;
417 snapshot.emplace(data_[offset]);
419 const T& src = snapshot ? *snapshot : value;
421 if (index == vsize_) {
422 std::allocator_traits<Allocator>::construct(alloc_, data_ + index, src);
424 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_,
425 std::move(data_[vsize_ - 1]));
426 for (std::size_t i = vsize_ - 1; i > index; --i) {
427 data_[i] = std::move(data_[i - 1]);
433 std::size_t old = vsize_;
435 notify({EventType::INSERT, index, old, vsize_});
436 return iterator(data_ + index);
439template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
441 requires std::destructible<T>
442typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::iterator
443Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::insert(const_iterator pos, T&& value) {
444 std::size_t index = pos - cbegin();
445 const bool willReallocate = (vsize_ == vcap_);
446 const bool willShift = (index != vsize_);
448 if (!willReallocate && !willShift) {
449 // Fast path: plain append with capacity to spare.
450 std::allocator_traits<Allocator>::construct(alloc_, data_ + index, std::move(value));
452 std::ptrdiff_t offset = -1;
453 if (data_ && std::addressof(value) >= data_ && std::addressof(value) < data_ + vsize_) {
454 offset = std::addressof(value) - data_;
457 if (willReallocate) {
458 reallocate(growCapacity());
461 std::optional<T> snapshot;
463 snapshot.emplace(std::move(data_[offset]));
466 if (index == vsize_) {
468 std::allocator_traits<Allocator>::construct(alloc_, data_ + index,
469 std::move(*snapshot));
471 std::allocator_traits<Allocator>::construct(alloc_, data_ + index,
475 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_,
476 std::move(data_[vsize_ - 1]));
477 for (std::size_t i = vsize_ - 1; i > index; --i) {
478 data_[i] = std::move(data_[i - 1]);
482 data_[index] = std::move(*snapshot);
484 data_[index] = std::move(value);
489 std::size_t old = vsize_;
491 notify({EventType::INSERT, index, old, vsize_});
492 return iterator(data_ + index);
495template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
497 requires std::destructible<T>
498template <std::input_iterator It>
499typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::iterator
500Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::insert(const_iterator pos, It first,
502 std::size_t index = pos - cbegin();
503 std::size_t i = index;
505 for (; first != last; ++first, ++i) {
506 (void)insert(cbegin() + i, *first);
509 return iterator(data_ + index);
512template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
514 requires std::destructible<T>
515template <typename... Args>
516 requires std::constructible_from<T, Args...>
517typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::iterator
518Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::emplace(const_iterator pos,
520 std::size_t index = pos - cbegin();
522 if (vsize_ == vcap_) {
523 reallocate(growCapacity());
526 if (index == vsize_) {
527 std::allocator_traits<Allocator>::construct(alloc_, data_ + index,
528 std::forward<Args>(args)...);
530 T tmp(std::forward<Args>(args)...);
532 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_,
533 std::move(data_[vsize_ - 1]));
534 for (std::size_t i = vsize_ - 1; i > index; --i) {
535 data_[i] = std::move(data_[i - 1]);
538 data_[index] = std::move(tmp);
541 std::size_t old = vsize_;
543 notify({EventType::INSERT, index, old, vsize_});
544 return iterator(data_ + index);
547template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
549 requires std::destructible<T>
550template <typename Predicate>
551 requires std::predicate<Predicate, const T&>
552std::size_t Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::remove_if(Predicate pred) {
553 std::size_t old = vsize_;
554 std::size_t dest = 0;
556 for (std::size_t i = 0; i < vsize_; ++i) {
557 if (!pred(data_[i])) {
559 data_[dest] = std::move(data_[i]);
563 // Removed elements are intentionally left untouched here — the
564 // cleanup loop below destroys every leftover slot in [dest, vsize_)
565 // exactly once, whether it holds an original removed element or a
566 // moved-from "kept" element. Destroying here too caused a
567 // double-destroy for any removed index >= the final `dest`.
570 for (std::size_t i = dest; i < vsize_; ++i) {
571 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
575 notify({EventType::REMOVE, dest, old, vsize_});
579template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
581 requires std::destructible<T>
582void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::pop_back() {
587 std::size_t old = vsize_;
588 std::allocator_traits<Allocator>::destroy(alloc_, data_ + vsize_ - 1);
591 notify({EventType::POPBACK, vsize_, old, vsize_});
594template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
596 requires std::destructible<T>
597typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::iterator
598Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::erase(const_iterator pos) {
599 std::size_t index = pos - cbegin();
601 for (std::size_t i = index; i + 1 < vsize_; ++i) {
602 data_[i] = std::move(data_[i + 1]);
605 std::allocator_traits<Allocator>::destroy(alloc_, data_ + vsize_ - 1);
607 std::size_t old = vsize_;
610 notify({EventType::ERASE, index, old, vsize_});
611 return iterator(data_ + index);
614template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
616 requires std::destructible<T>
617typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::iterator
618Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::erase(const_iterator first,
619 const_iterator last) {
620 std::size_t indexFirst = first - cbegin();
621 std::size_t indexLast = last - cbegin();
622 std::size_t count = indexLast - indexFirst;
625 return iterator(data_ + indexFirst);
628 for (std::size_t i = indexFirst; i + count < vsize_; ++i) {
629 data_[i] = std::move(data_[i + count]);
632 for (std::size_t i = vsize_ - count; i < vsize_; ++i) {
633 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
636 std::size_t old = vsize_;
639 notify({EventType::ERASE, indexFirst, old, vsize_});
640 return iterator(data_ + indexFirst);
643template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
645 requires std::destructible<T>
646void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::clear() noexcept {
651 for (std::size_t i = 0; i < vsize_; ++i) {
652 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
655 std::size_t old = vsize_;
658 notify({EventType::CLEAR, 0, old, 0});
661template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
663 requires std::destructible<T>
664void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::reserve(std::size_t newCap) {
665 if (newCap <= vcap_) {
669 std::size_t old = vcap_;
672 notify({EventType::RESERVE, 0, old, vcap_});
675template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
677 requires std::destructible<T>
678void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::shrink_to_fit() {
679 if (vsize_ == vcap_) {
683 std::size_t old = vcap_;
686 notify({EventType::SHRINK, 0, old, vcap_});
689// ============================================================
690// Section 4 — Observer
691// ============================================================
693template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
695 requires std::destructible<T>
697 requires EnableEvents && Listener<F, Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>>
698typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::ListenerHandle
699Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::subscribe(F&& listeners) {
700 if (listenerStore_.lsize_ == listenerStore_.lcap_) {
701 std::size_t newCap = listenerStore_.lcap_ == 0 ? INITIAL_CAP : listenerStore_.lcap_ * 2;
702 auto* newBuf = new ListenerFn[newCap];
704 for (std::size_t i = 0; i < listenerStore_.lsize_; ++i) {
705 newBuf[i] = std::move(listenerStore_.listeners_[i]);
708 delete[] listenerStore_.listeners_;
709 listenerStore_.listeners_ = newBuf;
710 listenerStore_.lcap_ = newCap;
713 listenerStore_.listeners_[listenerStore_.lsize_] = std::forward<F>(listeners);
714 return listenerStore_.lsize_++;
717template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
719 requires std::destructible<T>
720void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::unsubscribe(ListenerHandle handle)
721 requires EnableEvents
723 if (handle >= listenerStore_.lsize_) {
727 for (std::size_t i = handle; i < listenerStore_.lsize_ - 1; ++i) {
728 listenerStore_.listeners_[i] = std::move(listenerStore_.listeners_[i + 1]);
731 listenerStore_.listeners_[listenerStore_.lsize_ - 1] = ListenerFn{};
732 --listenerStore_.lsize_;
735// ============================================================
736// Section 5 — Comparison
737// ============================================================//
738// ============================================================
740template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
742 requires std::destructible<T>
743bool Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::operator==(const Vector& other) const
744 noexcept(noexcept(std::declval<const T&>() == std::declval<const T&>())) {
745 if (vsize_ != other.vsize_) {
753 // Fast path: for trivially-copyable T with the default allocator, the
754 // underlying storage is a flat byte buffer, so a single memcmp() over
755 // the whole range is equivalent to comparing element-by-element but
756 // lets the CPU compare many bytes per instruction instead of one T at
757 // a time. std::equal() can't take this shortcut here because it only
758 // recognizes libstdc++'s own vector iterator as "really a pointer";
759 // our Iterator type is a true contiguous_iterator but isn't special-cased
760 // by that internal check, so without this branch it falls back to a
761 // scalar, element-by-element loop.
762 if constexpr (std::is_trivially_copyable_v<T> && std::is_same_v<Allocator, std::allocator<T>>) {
763 return std::memcmp(std::to_address(data_), std::to_address(other.data_),
764 vsize_ * sizeof(T)) == 0;
766 return std::equal(begin(), end(), other.begin());
770template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
772 requires std::destructible<T>
773auto Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::operator<=>(const Vector& other)
774 const noexcept(noexcept(std::declval<const T&>() <=> std::declval<const T&>())) {
775 return std::lexicographical_compare_three_way(begin(), end(), other.begin(), other.end());
778// ============================================================
779// Section 6 — Span Access
780// ============================================================
782template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
784 requires std::destructible<T>
785std::span<T> Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::as_span() noexcept {
786 return std::span<T>(data_, vsize_);
789template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
791 requires std::destructible<T>
793Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::as_span() const noexcept {
794 return std::span<const T>(data_, vsize_);
797// ============================================================
798// Section 7 — Element Access
799// ============================================================
801template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
803 requires std::destructible<T>
804typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::pointer
805Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::data_ptr() noexcept {
809template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
811 requires std::destructible<T>
812typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_pointer
813Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::data_ptr() const noexcept {
817template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
819 requires std::destructible<T>
820typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::reference
821Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::at(std::size_t index) {
822 if (index >= vsize_) {
823 throw std::out_of_range("Vector::at() index out of range");
829template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
831 requires std::destructible<T>
832typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_reference
833Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::at(std::size_t index) const {
834 if (index >= vsize_) {
835 throw std::out_of_range("Vector::at() index out of range");
841template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
843 requires std::destructible<T>
844typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::reference
845Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::front() {
849template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
851 requires std::destructible<T>
852typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_reference
853Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::front() const {
857template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
859 requires std::destructible<T>
860typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::reference
861Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::back() {
862 return data_[vsize_ - 1];
865template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
867 requires std::destructible<T>
868typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_reference
869Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::back() const {
870 return data_[vsize_ - 1];
873template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
875 requires std::destructible<T>
876typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::reference
877Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::operator[](std::size_t index) noexcept {
881template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
883 requires std::destructible<T>
884typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_reference
885Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::operator[](
886 std::size_t index) const noexcept {
890// ============================================================
892// ============================================================
894template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
896 requires std::destructible<T>
897bool Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::contains(const T& value) const
898 noexcept(noexcept(std::declval<const T&>() == std::declval<const T&>())) {
903 // std::find() on a raw pointer range picks up libstdc++'s internal
904 // fast paths (manual loop-unrolling for scalar types, memchr for
905 // byte-sized types) that our own hand-written loop doesn't get for free.
906 const T* first = std::to_address(data_);
907 return std::find(first, first + vsize_, value) != first + vsize_;
910template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
912 requires std::destructible<T>
913typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::iterator
914Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::find(const T& value) noexcept(
915 noexcept(std::declval<const T&>() == std::declval<const T&>())) {
916 T* first = std::to_address(data_);
917 return iterator(std::find(first, first + vsize_, value));
920template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
922 requires std::destructible<T>
923typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_iterator
924Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::find(const T& value) const
925 noexcept(noexcept(std::declval<const T&>() == std::declval<const T&>())) {
926 const T* first = std::to_address(data_);
927 return const_iterator(std::find(first, first + vsize_, value));
930// ============================================================
932// ============================================================
934template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
936 requires std::destructible<T>
937void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::swap(Vector& other) noexcept {
939 swap(alloc_, other.alloc_);
940 swap(data_, other.data_);
941 swap(vsize_, other.vsize_);
942 swap(vcap_, other.vcap_);
943 swap(listenerStore_, other.listenerStore_);
946// ============================================================
947// Section 10 — Capacity
948// ============================================================
950template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
952 requires std::destructible<T>
953bool Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::empty() const noexcept {
957template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
959 requires std::destructible<T>
960std::size_t Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::size() const noexcept {
964template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
966 requires std::destructible<T>
967std::size_t Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::capacity() const noexcept {
971// ============================================================
972// Section 11 — Iterator Access
973// ============================================================
975template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
977 requires std::destructible<T>
978typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::iterator
979Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::begin() noexcept {
980 return iterator(data_);
983template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
985 requires std::destructible<T>
986typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::iterator
987Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::end() noexcept {
988 return iterator(data_ + vsize_);
991template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
993 requires std::destructible<T>
994typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_iterator
995Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::begin() const noexcept {
996 return const_iterator(data_);
999template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1001 requires std::destructible<T>
1002typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_iterator
1003Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::end() const noexcept {
1004 return const_iterator(data_ + vsize_);
1007template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1009 requires std::destructible<T>
1010typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_iterator
1011Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::cbegin() const noexcept {
1012 return const_iterator(data_);
1015template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1017 requires std::destructible<T>
1018typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_iterator
1019Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::cend() const noexcept {
1020 return const_iterator(data_ + vsize_);
1023template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1025 requires std::destructible<T>
1026typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::reverse_iterator
1027Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::rbegin() noexcept {
1028 return reverse_iterator(end());
1031template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1033 requires std::destructible<T>
1034typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::reverse_iterator
1035Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::rend() noexcept {
1036 return reverse_iterator(begin());
1039template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1041 requires std::destructible<T>
1042typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_reverse_iterator
1043Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::rbegin() const noexcept {
1044 return const_reverse_iterator(end());
1047template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1049 requires std::destructible<T>
1050typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_reverse_iterator
1051Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::rend() const noexcept {
1052 return const_reverse_iterator(begin());
1055template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1057 requires std::destructible<T>
1058typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_reverse_iterator
1059Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::crbegin() const noexcept {
1060 return const_reverse_iterator(end());
1063template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1065 requires std::destructible<T>
1066typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_reverse_iterator
1067Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::crend() const noexcept {
1068 return const_reverse_iterator(begin());
1071// ============================================================
1072// Section 12 — Private Helpers
1073// ============================================================
1075template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1077 requires std::destructible<T>
1078void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::release() noexcept {
1079 for (std::size_t i = 0; i < vsize_; ++i) {
1080 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
1084 std::allocator_traits<Allocator>::deallocate(alloc_, data_, vcap_);
1092template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1094 requires std::destructible<T>
1095void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::copyBufferFrom(const Vector& other) {
1096 if (other.vcap_ == 0) {
1100 data_ = std::allocator_traits<Allocator>::allocate(alloc_, other.vcap_);
1101 vcap_ = other.vcap_;
1103 if constexpr (std::is_trivially_copyable_v<T> && std::is_same_v<Allocator, std::allocator<T>>) {
1104 if (other.vsize_ > 0) {
1105 std::memcpy(std::to_address(data_), std::to_address(other.data_),
1106 other.vsize_ * sizeof(T));
1109 std::size_t constructed = 0;
1111 for (; constructed < other.vsize_; ++constructed) {
1112 std::allocator_traits<Allocator>::construct(alloc_, data_ + constructed,
1113 other.data_[constructed]);
1116 for (std::size_t i = 0; i < constructed; ++i) {
1117 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
1119 std::allocator_traits<Allocator>::deallocate(alloc_, data_, vcap_);
1126 vsize_ = other.vsize_;
1129template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1131 requires std::destructible<T>
1133Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::growCapacity() const noexcept {
1138 constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
1139 if (vcap_ > kMax / GrowthNum) {
1143 std::size_t grown = vcap_ * GrowthNum / GrowthDen;
1145 // Integer division can truncate back down to the current capacity for
1146 // small vcap_ values combined with a fractional growth ratio (e.g.
1147 // 1 * 3 / 2 == 1). Guarantee growth always strictly increases capacity
1148 // by at least one element, saturating at SIZE_MAX instead of overflowing.
1149 if (grown <= vcap_) {
1150 grown = (vcap_ < kMax) ? vcap_ + 1 : kMax;
1156template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1158 requires std::destructible<T>
1159void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::reallocate(std::size_t newCap) {
1160 pointer newData = std::allocator_traits<Allocator>::allocate(alloc_, newCap);
1162 if constexpr (std::is_trivially_copyable_v<T> && std::is_same_v<Allocator, std::allocator<T>>) {
1164 std::memcpy(std::to_address(newData), std::to_address(data_), vsize_ * sizeof(T));
1167 std::size_t constructed = 0;
1169 for (; constructed < vsize_; ++constructed) {
1170 std::allocator_traits<Allocator>::construct(
1171 alloc_, newData + constructed, std::move_if_noexcept(data_[constructed]));
1174 for (std::size_t i = 0; i < constructed; ++i) {
1175 std::allocator_traits<Allocator>::destroy(alloc_, newData + i);
1177 std::allocator_traits<Allocator>::deallocate(alloc_, newData, newCap);
1181 for (std::size_t i = 0; i < vsize_; ++i) {
1182 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
1187 std::allocator_traits<Allocator>::deallocate(alloc_, data_, vcap_);
1194template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1196 requires std::destructible<T>
1197void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::notify(EventData data) {
1198 // `if constexpr` here is what makes EnableEvents = false a true zero-cost
1199 // path: unlike a runtime `if (lsize_ == 0) return;`, this branch is
1200 // discarded during compilation, not just at runtime. There's no leftover
1201 // load of lsize_, no branch, and no reason for the compiler to leave a
1202 // real function call behind — the whole call site collapses to nothing.
1203 if constexpr (EnableEvents) {
1204 for (std::size_t i = 0; i < listenerStore_.lsize_; ++i) {
1205 if (listenerStore_.listeners_[i]) {
1206 listenerStore_.listeners_[i](*this, data);
1212// ============================================================
1213// Section 13 — Non-member Functions
1214// ============================================================
1216template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1218void swap(Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>& a,
1219 Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>& b) noexcept {
1223} // namespace VectorPro