VectorPro
High-performance, header-only dynamic array with configurable growth policy, custom allocator support, and optional modification-event notifications.
Loading...
Searching...
No Matches
Vector.tpp
Go to the documentation of this file.
1/**
2 * @file VectorPro.tpp
3 * @brief Vector template implementation.
4 *
5 * Contains the implementation of VectorPro::Vector template member
6 * functions and internal implementation details.
7 */
8
9// ============================================================
10// Vector.tpp
11// Template implementation for VectorPro::Vector.
12// ============================================================
13//
14// Sections:
15// 1. Constructors & Destructor
16// 2. Copy & Move Semantics
17// 3. Modifiers
18// 4. Observer
19// 5. Comparison
20// 6. Span Access
21// 7. Element Access
22// 8. Lookup
23// 9. Swap
24// 10. Capacity
25// 11. Iterator Access
26// 12. Private Helpers
27// 13. Non-member Functions
28//
29// ============================================================
30
31namespace VectorPro {
32
33// ============================================================
34// Section 1 — Constructors & Destructor
35// ============================================================
36
37template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
38 bool EnableEvents>
39 requires std::destructible<T>
40Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::Vector(std::size_t count,
41 const T& value) {
42 if (count == 0) {
43 return;
44 }
45
46 data_ = std::allocator_traits<Allocator>::allocate(alloc_, count);
47 vcap_ = count;
48
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);
53 } else {
54 for (std::size_t i = 0; i < count; ++i) {
55 std::allocator_traits<Allocator>::construct(alloc_, data_ + i, value);
56 }
57 }
58
59 vsize_ = count;
60}
61
62template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
63 bool EnableEvents>
64 requires std::destructible<T>
65Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::Vector(std::initializer_list<T> init) {
66 if (init.size() == 0) {
67 return;
68 }
69
70 data_ = std::allocator_traits<Allocator>::allocate(alloc_, init.size());
71 vcap_ = init.size();
72
73 for (const auto& val : init) {
74 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_++, val);
75 }
76}
77
78template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
79 bool EnableEvents>
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) {
84 push_back(*first);
85 }
86}
87
88template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
89 bool EnableEvents>
90 requires std::destructible<T>
91Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::Vector(const Allocator& alloc)
92 : alloc_(alloc) {}
93
94template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
95 bool EnableEvents>
96 requires std::destructible<T>
97Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::~Vector() noexcept {
98 release();
99
100 if constexpr (EnableEvents) {
101 if (listenerStore_.listeners_) {
102 delete[] listenerStore_.listeners_;
103 listenerStore_.listeners_ = nullptr;
104 }
105 }
106}
107
108// ============================================================
109// Section 2 — Copy & Move Semantics
110// ============================================================
111
112template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
113 bool EnableEvents>
114 requires std::destructible<T>
115Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::Vector(const Vector& other)
116 : alloc_(
117 std::allocator_traits<Allocator>::select_on_container_copy_construction(other.alloc_)) {
118 copyBufferFrom(other);
119}
120
121template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
122 bool EnableEvents>
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) {
133 return *this;
134 }
135
136 constexpr bool kPropagate =
137 std::allocator_traits<Allocator>::propagate_on_container_copy_assignment::value;
138
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.
144 release();
145 alloc_ = other.alloc_;
146 }
147 }
148
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));
156 }
157 for (std::size_t i = other.vsize_; i < vsize_; ++i) {
158 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
159 }
160 } else {
161 std::size_t common = std::min(vsize_, other.vsize_);
162
163 for (std::size_t i = 0; i < common; ++i) {
164 data_[i] = other.data_[i];
165 }
166
167 if (other.vsize_ > vsize_) {
168 std::size_t constructed = vsize_;
169 try {
170 for (; constructed < other.vsize_; ++constructed) {
171 std::allocator_traits<Allocator>::construct(alloc_, data_ + constructed,
172 other.data_[constructed]);
173 }
174 } catch (...) {
175 for (std::size_t i = vsize_; i < constructed; ++i) {
176 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
177 }
178 throw; // basic guarantee: vsize_ left at its pre-call value
179 }
180 } else {
181 for (std::size_t i = other.vsize_; i < vsize_; ++i) {
182 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
183 }
184 }
185 }
186
187 vsize_ = other.vsize_;
188 } else {
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.
194 Vector tmp;
195 tmp.alloc_ = kPropagate
196 ? std::allocator_traits<Allocator>::select_on_container_copy_construction(
197 other.alloc_)
198 : alloc_;
199 tmp.copyBufferFrom(other);
200 swap(tmp);
201 }
202
203 return *this;
204}
205
206template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
207 bool EnableEvents>
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;
213 other.vsize_ = 0;
214 other.vcap_ = 0;
215 if constexpr (EnableEvents) {
216 other.listenerStore_.listeners_ = nullptr;
217 other.listenerStore_.lsize_ = 0;
218 other.listenerStore_.lcap_ = 0;
219 }
220}
221
222template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
223 bool EnableEvents>
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;
235
236 const bool canSteal = kPropagate || kAlwaysEqual || (alloc_ == other.alloc_);
237
238 release();
239 if constexpr (EnableEvents) {
240 if (listenerStore_.listeners_) {
241 delete[] listenerStore_.listeners_;
242 listenerStore_.listeners_ = nullptr;
243 }
244 }
245
246 if (canSteal) {
247 if constexpr (kPropagate) {
248 alloc_ = std::move(other.alloc_);
249 }
250
251 data_ = other.data_;
252 vsize_ = other.vsize_;
253 vcap_ = other.vcap_;
254 if constexpr (EnableEvents) {
255 listenerStore_ = other.listenerStore_;
256 }
257
258 other.data_ = nullptr;
259 other.vsize_ = 0;
260 other.vcap_ = 0;
261 if constexpr (EnableEvents) {
262 other.listenerStore_.listeners_ = nullptr;
263 other.listenerStore_.lsize_ = 0;
264 other.listenerStore_.lcap_ = 0;
265 }
266 } else {
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]));
275 }
276 vsize_ = other.vsize_;
277 }
278 other.release();
279 }
280 }
281
282 return *this;
283}
284
285// ============================================================
286// Section 3 — Modifiers
287// ============================================================
288
289template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
290 bool EnableEvents>
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);
298 return;
299 }
300 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_, value);
301 std::size_t old = vsize_;
302 ++vsize_;
303 notify({EventType::PUSHBACK, old, old, vsize_});
304}
305
306template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
307 bool EnableEvents>
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_;
316 }
317
318 reallocate(growCapacity());
319
320 const T& src = (offset >= 0) ? data_[offset] : value;
321 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_, src);
322
323 std::size_t old = vsize_;
324 ++vsize_;
325 notify({EventType::PUSHBACK, old, old, vsize_});
326}
327
328template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
329 bool EnableEvents>
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));
334 return;
335 }
336 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_, std::move(value));
337 std::size_t old = vsize_;
338 ++vsize_;
339 notify({EventType::PUSHBACK, old, old, vsize_});
340}
341
342template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
343 bool EnableEvents>
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_;
352 }
353
354 reallocate(growCapacity());
355
356 if (offset >= 0) {
357 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_,
358 std::move(data_[offset]));
359 } else {
360 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_, std::move(value));
361 }
362
363 std::size_t old = vsize_;
364 ++vsize_;
365 notify({EventType::PUSHBACK, old, old, vsize_});
366}
367
368template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
369 bool EnableEvents>
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());
376 }
377
378 std::allocator_traits<Allocator>::construct(alloc_, data_ + vsize_,
379 std::forward<Args>(args)...);
380
381 std::size_t old = vsize_;
382 ++vsize_;
383 notify({EventType::EMPLACEBACK, old, old, vsize_});
384}
385
386template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
387 bool EnableEvents>
388 requires std::destructible<T>
389typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::iterator
390Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::insert(const_iterator pos,
391 const T& value) {
392 std::size_t index = pos - cbegin();
393 const bool willReallocate = (vsize_ == vcap_);
394 const bool willShift = (index != vsize_);
395
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);
400 } else {
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_;
407 }
408
409 if (willReallocate) {
410 reallocate(growCapacity());
411 }
412
413 // Snapshot before shifting — the shift loop can overwrite `offset`
414 // before we'd otherwise read it.
415 std::optional<T> snapshot;
416 if (offset >= 0) {
417 snapshot.emplace(data_[offset]);
418 }
419 const T& src = snapshot ? *snapshot : value;
420
421 if (index == vsize_) {
422 std::allocator_traits<Allocator>::construct(alloc_, data_ + index, src);
423 } else {
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]);
428 }
429 data_[index] = src;
430 }
431 }
432
433 std::size_t old = vsize_;
434 ++vsize_;
435 notify({EventType::INSERT, index, old, vsize_});
436 return iterator(data_ + index);
437}
438
439template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
440 bool EnableEvents>
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_);
447
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));
451 } else {
452 std::ptrdiff_t offset = -1;
453 if (data_ && std::addressof(value) >= data_ && std::addressof(value) < data_ + vsize_) {
454 offset = std::addressof(value) - data_;
455 }
456
457 if (willReallocate) {
458 reallocate(growCapacity());
459 }
460
461 std::optional<T> snapshot;
462 if (offset >= 0) {
463 snapshot.emplace(std::move(data_[offset]));
464 }
465
466 if (index == vsize_) {
467 if (snapshot) {
468 std::allocator_traits<Allocator>::construct(alloc_, data_ + index,
469 std::move(*snapshot));
470 } else {
471 std::allocator_traits<Allocator>::construct(alloc_, data_ + index,
472 std::move(value));
473 }
474 } else {
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]);
479 }
480
481 if (snapshot) {
482 data_[index] = std::move(*snapshot);
483 } else {
484 data_[index] = std::move(value);
485 }
486 }
487 }
488
489 std::size_t old = vsize_;
490 ++vsize_;
491 notify({EventType::INSERT, index, old, vsize_});
492 return iterator(data_ + index);
493}
494
495template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
496 bool EnableEvents>
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,
501 It last) {
502 std::size_t index = pos - cbegin();
503 std::size_t i = index;
504
505 for (; first != last; ++first, ++i) {
506 (void)insert(cbegin() + i, *first);
507 }
508
509 return iterator(data_ + index);
510}
511
512template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
513 bool EnableEvents>
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,
519 Args&&... args) {
520 std::size_t index = pos - cbegin();
521
522 if (vsize_ == vcap_) {
523 reallocate(growCapacity());
524 }
525
526 if (index == vsize_) {
527 std::allocator_traits<Allocator>::construct(alloc_, data_ + index,
528 std::forward<Args>(args)...);
529 } else {
530 T tmp(std::forward<Args>(args)...);
531
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]);
536 }
537
538 data_[index] = std::move(tmp);
539 }
540
541 std::size_t old = vsize_;
542 ++vsize_;
543 notify({EventType::INSERT, index, old, vsize_});
544 return iterator(data_ + index);
545}
546
547template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
548 bool EnableEvents>
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;
555
556 for (std::size_t i = 0; i < vsize_; ++i) {
557 if (!pred(data_[i])) {
558 if (dest != i) {
559 data_[dest] = std::move(data_[i]);
560 }
561 ++dest;
562 }
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`.
568 }
569
570 for (std::size_t i = dest; i < vsize_; ++i) {
571 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
572 }
573
574 vsize_ = dest;
575 notify({EventType::REMOVE, dest, old, vsize_});
576 return old - vsize_;
577}
578
579template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
580 bool EnableEvents>
581 requires std::destructible<T>
582void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::pop_back() {
583 if (vsize_ == 0) {
584 return;
585 }
586
587 std::size_t old = vsize_;
588 std::allocator_traits<Allocator>::destroy(alloc_, data_ + vsize_ - 1);
589 --vsize_;
590
591 notify({EventType::POPBACK, vsize_, old, vsize_});
592}
593
594template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
595 bool EnableEvents>
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();
600
601 for (std::size_t i = index; i + 1 < vsize_; ++i) {
602 data_[i] = std::move(data_[i + 1]);
603 }
604
605 std::allocator_traits<Allocator>::destroy(alloc_, data_ + vsize_ - 1);
606
607 std::size_t old = vsize_;
608 --vsize_;
609
610 notify({EventType::ERASE, index, old, vsize_});
611 return iterator(data_ + index);
612}
613
614template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
615 bool EnableEvents>
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;
623
624 if (count == 0) {
625 return iterator(data_ + indexFirst);
626 }
627
628 for (std::size_t i = indexFirst; i + count < vsize_; ++i) {
629 data_[i] = std::move(data_[i + count]);
630 }
631
632 for (std::size_t i = vsize_ - count; i < vsize_; ++i) {
633 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
634 }
635
636 std::size_t old = vsize_;
637 vsize_ -= count;
638
639 notify({EventType::ERASE, indexFirst, old, vsize_});
640 return iterator(data_ + indexFirst);
641}
642
643template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
644 bool EnableEvents>
645 requires std::destructible<T>
646void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::clear() noexcept {
647 if (vsize_ == 0) {
648 return;
649 }
650
651 for (std::size_t i = 0; i < vsize_; ++i) {
652 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
653 }
654
655 std::size_t old = vsize_;
656 vsize_ = 0;
657
658 notify({EventType::CLEAR, 0, old, 0});
659}
660
661template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
662 bool EnableEvents>
663 requires std::destructible<T>
664void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::reserve(std::size_t newCap) {
665 if (newCap <= vcap_) {
666 return;
667 }
668
669 std::size_t old = vcap_;
670 reallocate(newCap);
671
672 notify({EventType::RESERVE, 0, old, vcap_});
673}
674
675template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
676 bool EnableEvents>
677 requires std::destructible<T>
678void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::shrink_to_fit() {
679 if (vsize_ == vcap_) {
680 return;
681 }
682
683 std::size_t old = vcap_;
684 reallocate(vsize_);
685
686 notify({EventType::SHRINK, 0, old, vcap_});
687}
688
689// ============================================================
690// Section 4 — Observer
691// ============================================================
692
693template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
694 bool EnableEvents>
695 requires std::destructible<T>
696template <typename F>
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];
703
704 for (std::size_t i = 0; i < listenerStore_.lsize_; ++i) {
705 newBuf[i] = std::move(listenerStore_.listeners_[i]);
706 }
707
708 delete[] listenerStore_.listeners_;
709 listenerStore_.listeners_ = newBuf;
710 listenerStore_.lcap_ = newCap;
711 }
712
713 listenerStore_.listeners_[listenerStore_.lsize_] = std::forward<F>(listeners);
714 return listenerStore_.lsize_++;
715}
716
717template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
718 bool EnableEvents>
719 requires std::destructible<T>
720void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::unsubscribe(ListenerHandle handle)
721 requires EnableEvents
722{
723 if (handle >= listenerStore_.lsize_) {
724 return;
725 }
726
727 for (std::size_t i = handle; i < listenerStore_.lsize_ - 1; ++i) {
728 listenerStore_.listeners_[i] = std::move(listenerStore_.listeners_[i + 1]);
729 }
730
731 listenerStore_.listeners_[listenerStore_.lsize_ - 1] = ListenerFn{};
732 --listenerStore_.lsize_;
733}
734
735// ============================================================
736// Section 5 — Comparison
737// ============================================================//
738// ============================================================
739
740template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
741 bool EnableEvents>
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_) {
746 return false;
747 }
748
749 if (vsize_ == 0) {
750 return true;
751 }
752
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;
765 } else {
766 return std::equal(begin(), end(), other.begin());
767 }
768}
769
770template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
771 bool EnableEvents>
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());
776}
777
778// ============================================================
779// Section 6 — Span Access
780// ============================================================
781
782template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
783 bool EnableEvents>
784 requires std::destructible<T>
785std::span<T> Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::as_span() noexcept {
786 return std::span<T>(data_, vsize_);
787}
788
789template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
790 bool EnableEvents>
791 requires std::destructible<T>
792std::span<const T>
793Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::as_span() const noexcept {
794 return std::span<const T>(data_, vsize_);
795}
796
797// ============================================================
798// Section 7 — Element Access
799// ============================================================
800
801template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
802 bool EnableEvents>
803 requires std::destructible<T>
804typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::pointer
805Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::data_ptr() noexcept {
806 return data_;
807}
808
809template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
810 bool EnableEvents>
811 requires std::destructible<T>
812typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_pointer
813Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::data_ptr() const noexcept {
814 return data_;
815}
816
817template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
818 bool EnableEvents>
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");
824 }
825
826 return data_[index];
827}
828
829template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
830 bool EnableEvents>
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");
836 }
837
838 return data_[index];
839}
840
841template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
842 bool EnableEvents>
843 requires std::destructible<T>
844typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::reference
845Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::front() {
846 return data_[0];
847}
848
849template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
850 bool EnableEvents>
851 requires std::destructible<T>
852typename Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::const_reference
853Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::front() const {
854 return data_[0];
855}
856
857template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
858 bool EnableEvents>
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];
863}
864
865template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
866 bool EnableEvents>
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];
871}
872
873template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
874 bool EnableEvents>
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 {
878 return data_[index];
879}
880
881template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
882 bool EnableEvents>
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 {
887 return data_[index];
888}
889
890// ============================================================
891// Section 8 — Lookup
892// ============================================================
893
894template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
895 bool EnableEvents>
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&>())) {
899 if (vsize_ == 0) {
900 return false;
901 }
902
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_;
908}
909
910template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
911 bool EnableEvents>
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));
918}
919
920template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
921 bool EnableEvents>
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));
928}
929
930// ============================================================
931// Section 9 — Swap
932// ============================================================
933
934template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
935 bool EnableEvents>
936 requires std::destructible<T>
937void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::swap(Vector& other) noexcept {
938 using std::swap;
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_);
944}
945
946// ============================================================
947// Section 10 — Capacity
948// ============================================================
949
950template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
951 bool EnableEvents>
952 requires std::destructible<T>
953bool Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::empty() const noexcept {
954 return vsize_ == 0;
955}
956
957template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
958 bool EnableEvents>
959 requires std::destructible<T>
960std::size_t Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::size() const noexcept {
961 return vsize_;
962}
963
964template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
965 bool EnableEvents>
966 requires std::destructible<T>
967std::size_t Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::capacity() const noexcept {
968 return vcap_;
969}
970
971// ============================================================
972// Section 11 — Iterator Access
973// ============================================================
974
975template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
976 bool EnableEvents>
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_);
981}
982
983template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
984 bool EnableEvents>
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_);
989}
990
991template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
992 bool EnableEvents>
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_);
997}
998
999template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1000 bool EnableEvents>
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_);
1005}
1006
1007template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1008 bool EnableEvents>
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_);
1013}
1014
1015template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1016 bool EnableEvents>
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_);
1021}
1022
1023template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1024 bool EnableEvents>
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());
1029}
1030
1031template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1032 bool EnableEvents>
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());
1037}
1038
1039template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1040 bool EnableEvents>
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());
1045}
1046
1047template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1048 bool EnableEvents>
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());
1053}
1054
1055template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1056 bool EnableEvents>
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());
1061}
1062
1063template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1064 bool EnableEvents>
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());
1069}
1070
1071// ============================================================
1072// Section 12 — Private Helpers
1073// ============================================================
1074
1075template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1076 bool EnableEvents>
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);
1081 }
1082
1083 if (data_) {
1084 std::allocator_traits<Allocator>::deallocate(alloc_, data_, vcap_);
1085 }
1086
1087 data_ = nullptr;
1088 vsize_ = 0;
1089 vcap_ = 0;
1090}
1091
1092template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1093 bool EnableEvents>
1094 requires std::destructible<T>
1095void Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::copyBufferFrom(const Vector& other) {
1096 if (other.vcap_ == 0) {
1097 return;
1098 }
1099
1100 data_ = std::allocator_traits<Allocator>::allocate(alloc_, other.vcap_);
1101 vcap_ = other.vcap_;
1102
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));
1107 }
1108 } else {
1109 std::size_t constructed = 0;
1110 try {
1111 for (; constructed < other.vsize_; ++constructed) {
1112 std::allocator_traits<Allocator>::construct(alloc_, data_ + constructed,
1113 other.data_[constructed]);
1114 }
1115 } catch (...) {
1116 for (std::size_t i = 0; i < constructed; ++i) {
1117 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
1118 }
1119 std::allocator_traits<Allocator>::deallocate(alloc_, data_, vcap_);
1120 data_ = nullptr;
1121 vcap_ = 0;
1122 throw;
1123 }
1124 }
1125
1126 vsize_ = other.vsize_;
1127}
1128
1129template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1130 bool EnableEvents>
1131 requires std::destructible<T>
1132std::size_t
1133Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>::growCapacity() const noexcept {
1134 if (vcap_ == 0) {
1135 return INITIAL_CAP;
1136 }
1137
1138 constexpr std::size_t kMax = std::numeric_limits<std::size_t>::max();
1139 if (vcap_ > kMax / GrowthNum) {
1140 return kMax;
1141 }
1142
1143 std::size_t grown = vcap_ * GrowthNum / GrowthDen;
1144
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;
1151 }
1152
1153 return grown;
1154}
1155
1156template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1157 bool EnableEvents>
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);
1161
1162 if constexpr (std::is_trivially_copyable_v<T> && std::is_same_v<Allocator, std::allocator<T>>) {
1163 if (vsize_ > 0) {
1164 std::memcpy(std::to_address(newData), std::to_address(data_), vsize_ * sizeof(T));
1165 }
1166 } else {
1167 std::size_t constructed = 0;
1168 try {
1169 for (; constructed < vsize_; ++constructed) {
1170 std::allocator_traits<Allocator>::construct(
1171 alloc_, newData + constructed, std::move_if_noexcept(data_[constructed]));
1172 }
1173 } catch (...) {
1174 for (std::size_t i = 0; i < constructed; ++i) {
1175 std::allocator_traits<Allocator>::destroy(alloc_, newData + i);
1176 }
1177 std::allocator_traits<Allocator>::deallocate(alloc_, newData, newCap);
1178 throw;
1179 }
1180
1181 for (std::size_t i = 0; i < vsize_; ++i) {
1182 std::allocator_traits<Allocator>::destroy(alloc_, data_ + i);
1183 }
1184 }
1185
1186 if (data_) {
1187 std::allocator_traits<Allocator>::deallocate(alloc_, data_, vcap_);
1188 }
1189
1190 data_ = newData;
1191 vcap_ = newCap;
1192}
1193
1194template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1195 bool EnableEvents>
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);
1207 }
1208 }
1209 }
1210}
1211
1212// ============================================================
1213// Section 13 — Non-member Functions
1214// ============================================================
1215
1216template <typename T, typename Allocator, std::size_t GrowthNum, std::size_t GrowthDen,
1217 bool EnableEvents>
1218void swap(Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>& a,
1219 Vector<T, Allocator, GrowthNum, GrowthDen, EnableEvents>& b) noexcept {
1220 a.swap(b);
1221}
1222
1223} // namespace VectorPro