Sane C++ Libraries
C++ Platform Abstraction Libraries
Vector.h
1// Copyright (c) Stefano Cristiano
2// SPDX-License-Identifier: MIT
3#pragma once
4#include "../Foundation/Assert.h"
5#include "../Foundation/Limits.h"
6#include "../Foundation/Memory.h"
7#include "../Foundation/PrimitiveTypes.h"
8#include "Internal/Segment.h"
9
10namespace SC
11{
12template <typename T>
13struct Vector;
14struct SC_COMPILER_EXPORT VectorAllocator;
15} // namespace SC
18
21struct SC::VectorAllocator
22{
23 static SegmentHeader* reallocate(SegmentHeader* oldHeader, size_t newSize);
24
25 static SegmentHeader* allocate(SegmentHeader* oldHeader, size_t numNewBytes, void* selfPointer);
26
27 static void release(SegmentHeader* oldHeader);
28
29 template <typename T>
30 static T* getItems(SegmentHeader* header)
31 {
32 return reinterpret_cast<T*>(reinterpret_cast<char*>(header) + sizeof(SegmentHeader));
33 }
34 template <typename T>
35 static const T* getItems(const SegmentHeader* header)
36 {
37 return reinterpret_cast<T*>(reinterpret_cast<const char*>(header) + sizeof(SegmentHeader));
38 }
39};
40
49template <typename T>
51{
52 protected:
53 SegmentItems<T>* getSegmentItems() const { return SegmentItems<T>::getSegment(items); }
54 template <int N>
55 friend struct SmallString;
56 friend struct String;
57 friend struct SmallStringTest;
58 friend struct VectorTest;
59 friend struct SmallVectorTest;
60 T* items;
61 using Operations = SegmentOperations<VectorAllocator, T>;
62
63 public:
65 Vector() : items(nullptr) {}
66
69 Vector(std::initializer_list<T> list) : items(nullptr) { (void)append({list.begin(), list.size()}); }
70
72 ~Vector() { destroy(); }
73
76 Vector(const Vector& other);
77
80 Vector(Vector&& other) noexcept;
81
86
90 Vector& operator=(const Vector& other);
91
94 [[nodiscard]] Span<const T> toSpanConst() const SC_LANGUAGE_LIFETIME_BOUND { return Span<const T>(items, size()); }
95
98 [[nodiscard]] Span<T> toSpan() SC_LANGUAGE_LIFETIME_BOUND { return Span<T>(items, size()); }
99
103 [[nodiscard]] T& operator[](size_t index) SC_LANGUAGE_LIFETIME_BOUND;
104
108 [[nodiscard]] const T& operator[](size_t index) const SC_LANGUAGE_LIFETIME_BOUND;
109
113 [[nodiscard]] bool push_front(const T& element) { return insert(0, {&element, 1}); }
114
118 [[nodiscard]] bool push_front(T&& element) { return insertMove(0, {&element, 1}); }
119
123 [[nodiscard]] bool push_back(const T& element) { return Operations::push_back(items, element); }
124
128 [[nodiscard]] bool push_back(T&& element) { return Operations::push_back(items, move(element)); }
129
132 [[nodiscard]] bool pop_back() { return Operations::pop_back(items); }
133
136 [[nodiscard]] bool pop_front() { return Operations::pop_front(items); }
137
140 [[nodiscard]] T& front() SC_LANGUAGE_LIFETIME_BOUND;
141
144 [[nodiscard]] const T& front() const SC_LANGUAGE_LIFETIME_BOUND;
145
148 [[nodiscard]] T& back() SC_LANGUAGE_LIFETIME_BOUND;
149
152 [[nodiscard]] const T& back() const SC_LANGUAGE_LIFETIME_BOUND;
153
157 [[nodiscard]] bool reserve(size_t newCapacity);
158
163 [[nodiscard]] bool resize(size_t newSize, const T& value = T());
164
170 [[nodiscard]] bool resizeWithoutInitializing(size_t newSize);
171
174 void clear();
175
178
181 [[nodiscard]] bool shrink_to_fit() { return Operations::shrink_to_fit(items); }
182
185 [[nodiscard]] bool isEmpty() const { return (items == nullptr) || getSegmentItems()->isEmpty(); }
186
189 [[nodiscard]] size_t size() const;
190
193 [[nodiscard]] size_t capacity() const;
194
197 [[nodiscard]] T* begin() SC_LANGUAGE_LIFETIME_BOUND { return items; }
200 [[nodiscard]] const T* begin() const SC_LANGUAGE_LIFETIME_BOUND { return items; }
203 [[nodiscard]] T* end() SC_LANGUAGE_LIFETIME_BOUND { return items + size(); }
206 [[nodiscard]] const T* end() const SC_LANGUAGE_LIFETIME_BOUND { return items + size(); }
209 [[nodiscard]] T* data() SC_LANGUAGE_LIFETIME_BOUND { return items; }
212 [[nodiscard]] const T* data() const SC_LANGUAGE_LIFETIME_BOUND { return items; }
213
218 [[nodiscard]] bool insert(size_t idx, Span<const T> data);
219
223 [[nodiscard]] bool append(Span<const T> data);
224
228 template <typename U>
229 [[nodiscard]] bool append(Span<const U> src);
230
235 template <typename U>
236 [[nodiscard]] bool appendMove(U&& src);
237
244 template <typename U>
245 [[nodiscard]] bool contains(const U& value, size_t* foundIndex = nullptr) const;
246
252 template <typename Lambda>
253 [[nodiscard]] bool find(Lambda&& lambda, size_t* foundIndex = nullptr) const;
254
258 [[nodiscard]] bool removeAt(size_t index) { return Operations::removeAt(items, index); }
259
264 template <typename Lambda>
265 [[nodiscard]] bool removeAll(Lambda&& criteria);
266
271 template <typename U>
272 [[nodiscard]] bool remove(const U& value);
273
274 private:
275 [[nodiscard]] bool insertMove(size_t idx, Span<T> data);
276
277 [[nodiscard]] bool appendMove(Span<T> data);
278
279 void destroy();
280
281 void moveAssign(Vector&& other);
282};
284
285//-----------------------------------------------------------------------------------------------------------------------
286// Implementation details
287//-----------------------------------------------------------------------------------------------------------------------
288
289//-----------------------------------------------------------------------------------------------------------------------
290// VectorAllocator
291//-----------------------------------------------------------------------------------------------------------------------
292
293inline SC::SegmentHeader* SC::VectorAllocator::reallocate(SegmentHeader* oldHeader, size_t newSize)
294{
295 if (newSize > SegmentHeader::MaxValue)
296 {
297 return nullptr;
298 }
299 SegmentHeader* newHeader;
300 if (oldHeader->isSmallVector)
301 {
302 newHeader = static_cast<SegmentHeader*>(Memory::allocate(sizeof(SegmentHeader) + newSize));
303 const auto minSize = min(newSize, static_cast<decltype(newSize)>(oldHeader->sizeBytes));
304 ::memcpy(newHeader, oldHeader, minSize + alignof(SegmentHeader));
305 newHeader->initDefaults();
306 newHeader->isFollowedBySmallVector = true;
307 }
308 else
309 {
310 newHeader = static_cast<SegmentHeader*>(Memory::reallocate(oldHeader, sizeof(SegmentHeader) + newSize));
311 }
312 if (newHeader)
313 {
314 newHeader->capacityBytes = static_cast<SegmentHeader::SizeType>(newSize);
315 }
316 return newHeader;
317}
318
319inline SC::SegmentHeader* SC::VectorAllocator::allocate(SegmentHeader* oldHeader, size_t numNewBytes, void* selfPointer)
320{
321 if (numNewBytes > SegmentHeader::MaxValue)
322 {
323 return nullptr;
324 }
325 if (oldHeader != nullptr)
326 {
327 if (oldHeader->isFollowedBySmallVector)
328 {
329 // If we were followed by a small vector, we check if that small vector has enough memory
330 SegmentHeader* followingHeader =
331 reinterpret_cast<SegmentHeader*>(static_cast<char*>(selfPointer) + alignof(SegmentHeader));
332 if (followingHeader->isSmallVector && followingHeader->capacityBytes >= numNewBytes)
333 {
334 return followingHeader;
335 }
336 }
337 else if (oldHeader->isSmallVector)
338 {
339 if (oldHeader->capacityBytes >= numNewBytes)
340 {
341 // shrink_to_fit on SmallVector pointing to internal buffer
342 return oldHeader;
343 }
344 }
345 }
346 SegmentHeader* newHeader = static_cast<SegmentHeader*>(Memory::allocate(sizeof(SegmentHeader) + numNewBytes));
347 if (newHeader)
348 {
349 newHeader->capacityBytes = static_cast<SegmentHeader::SizeType>(numNewBytes);
350 newHeader->initDefaults();
351 if (oldHeader != nullptr && oldHeader->isSmallVector)
352 {
353 newHeader->isFollowedBySmallVector = true;
354 }
355 }
356 return newHeader;
357}
358
359inline void SC::VectorAllocator::release(SegmentHeader* oldHeader)
360{
361 if (oldHeader->isSmallVector)
362 {
363 oldHeader->sizeBytes = 0;
364 }
365 else
366 {
367 Memory::release(oldHeader);
368 }
369}
370
371//-----------------------------------------------------------------------------------------------------------------------
372// Vector
373//-----------------------------------------------------------------------------------------------------------------------
374template <typename T>
375SC::Vector<T>::Vector(const Vector& other) : items(nullptr)
376{
377 static_assert(sizeof(Vector) == sizeof(void*), "sizeof(Vector)");
378 const size_t otherSize = other.size();
379 if (otherSize > 0)
380 {
381 const bool res = append(other.toSpanConst());
382 (void)res;
383 SC_ASSERT_DEBUG(res);
384 }
385}
386
387template <typename T>
389{
390 items = nullptr;
391 moveAssign(forward<Vector>(other));
392}
393
394template <typename T>
396{
397 if (&other != this)
398 {
399 moveAssign(forward<Vector>(other));
400 }
401 return *this;
402}
403
404template <typename T>
406{
407 if (&other != this)
408 {
409 const bool res = Operations::assign(items, other.data(), other.size());
410 (void)res;
411 SC_ASSERT_DEBUG(res);
412 }
413 return *this;
414}
415
416template <typename T>
417T& SC::Vector<T>::operator[](size_t index) SC_LANGUAGE_LIFETIME_BOUND
418{
419 SC_ASSERT_DEBUG(index < size());
420 return items[index];
421}
422
423template <typename T>
424const T& SC::Vector<T>::operator[](size_t index) const SC_LANGUAGE_LIFETIME_BOUND
425{
426 SC_ASSERT_DEBUG(index < size());
427 return items[index];
428}
429
430template <typename T>
431T& SC::Vector<T>::front() SC_LANGUAGE_LIFETIME_BOUND
432{
433 const size_t numElements = size();
434 SC_ASSERT_RELEASE(numElements > 0);
435 return items[0];
436}
437
438template <typename T>
439const T& SC::Vector<T>::front() const SC_LANGUAGE_LIFETIME_BOUND
440{
441 const size_t numElements = size();
442 SC_ASSERT_RELEASE(numElements > 0);
443 return items[0];
444}
445
446template <typename T>
447T& SC::Vector<T>::back() SC_LANGUAGE_LIFETIME_BOUND
448{
449 const size_t numElements = size();
450 SC_ASSERT_RELEASE(numElements > 0);
451 return items[numElements - 1];
452}
453
454template <typename T>
455const T& SC::Vector<T>::back() const SC_LANGUAGE_LIFETIME_BOUND
456{
457 const size_t numElements = size();
458 SC_ASSERT_RELEASE(numElements > 0);
459 return items[numElements - 1];
460}
461
462template <typename T>
463bool SC::Vector<T>::reserve(size_t newCapacity)
464{
465 return newCapacity > capacity() ? Operations::ensureCapacity(items, newCapacity, size()) : true;
466}
467
468template <typename T>
469bool SC::Vector<T>::resize(size_t newSize, const T& value)
470{
471 static constexpr bool IsTrivial = TypeTraits::IsTriviallyCopyable<T>::value;
472 return Operations::template resizeInternal<IsTrivial, true>(items, newSize, &value);
473}
474
475template <typename T>
477{
478 static constexpr bool IsTrivial = TypeTraits::IsTriviallyCopyable<T>::value;
479 return Operations::template resizeInternal<IsTrivial, false>(items, newSize, nullptr);
480}
481
482template <typename T>
484{
485 if (items != nullptr)
486 {
487 Operations::clear(getSegmentItems());
488 }
489}
490
491template <typename T>
493{
494 if (items == nullptr)
495 SC_LANGUAGE_UNLIKELY { return 0; }
496 else
497 {
498 return static_cast<size_t>(getSegmentItems()->sizeBytes / sizeof(T));
499 }
500}
501
502template <typename T>
504{
505 if (items == nullptr)
506 SC_LANGUAGE_UNLIKELY { return 0; }
507 else
508 {
509 return static_cast<size_t>(getSegmentItems()->capacityBytes / sizeof(T));
510 }
511}
512
513template <typename T>
515{
516 return Operations::template insert<true>(items, idx, data.data(), data.sizeInElements());
517}
518
519template <typename T>
521{
522 return Operations::template insert<true>(items, size(), data.data(), data.sizeInElements());
523}
524
525template <typename T>
526template <typename U>
528{
529 if (appendMove({src.data(), src.size()}))
530 {
531 src.clear();
532 return true;
533 }
534 return false;
535}
536
537// TODO: Check if this can be unified with the same version inside Segment
538template <typename T>
539template <typename U>
541{
542 const auto oldSize = size();
543 if (reserve(src.sizeInElements()))
544 {
545 for (auto& it : src)
546 {
547 if (not push_back(it))
548 {
549 break;
550 }
551 }
552 }
553 if (oldSize + src.sizeInElements() != size())
554 {
555 SC_ASSERT_RELEASE(resize(oldSize));
556 return false;
557 }
558 return true;
559}
560
561template <typename T>
562template <typename U>
563bool SC::Vector<T>::contains(const U& value, size_t* foundIndex) const
564{
566 items, 0, size(), [&](const T& element) { return element == value; }, foundIndex);
567}
568
569template <typename T>
570template <typename Lambda>
571bool SC::Vector<T>::find(Lambda&& lambda, size_t* foundIndex) const
572{
573 return SegmentItems<T>::findIf(items, 0, size(), forward<Lambda>(lambda), foundIndex);
574}
575
576template <typename T>
577template <typename Lambda>
578bool SC::Vector<T>::removeAll(Lambda&& criteria)
579{
580 return SegmentItems<T>::removeAll(items, 0, size(), forward<Lambda>(criteria));
581}
582
583template <typename T>
584template <typename U>
585bool SC::Vector<T>::remove(const U& value)
586{
587 return SegmentItems<T>::removeAll(items, 0, size(), [&](const auto& it) { return it == value; });
588}
589
590template <typename T>
592{
593 if (items != nullptr)
594 {
595 Operations::destroy(getSegmentItems());
596 }
597 items = nullptr;
598}
599
600template <typename T>
601bool SC::Vector<T>::insertMove(size_t idx, Span<T> data)
602{
603 return Operations::template insert<false>(items, idx, data.data(), data.sizeInElements());
604}
605
606template <typename T>
607bool SC::Vector<T>::appendMove(Span<T> data)
608{
609 return Operations::template insert<false>(items, size(), data.data(), data.sizeInElements());
610}
611
612template <typename T>
613void SC::Vector<T>::moveAssign(Vector&& other)
614{
615 SegmentHeader* otherHeader = other.items != nullptr ? SegmentHeader::getSegmentHeader(other.items) : nullptr;
616 const bool otherIsSmallVector = otherHeader != nullptr && otherHeader->isSmallVector;
617 if (otherIsSmallVector)
618 {
619 // We can't "move" the small vector, so we just assign its items
620 clear();
621 (void)appendMove({other.items, other.size()});
622 other.clear();
623 }
624 else
625 {
626 const bool otherWasFollowedBySmallVector = otherHeader != nullptr && otherHeader->isFollowedBySmallVector;
627 if (otherHeader != nullptr)
628 {
629 // Before grabbing other.items we want to remember our state of "followed by/being a small vector"
630 const SegmentHeader* oldHeader = items != nullptr ? SegmentHeader::getSegmentHeader(items) : nullptr;
631 const bool shouldStillBeFollowedBySmallVector =
632 oldHeader != nullptr && (oldHeader->isFollowedBySmallVector || oldHeader->isSmallVector);
633 otherHeader->isFollowedBySmallVector = shouldStillBeFollowedBySmallVector;
634 }
635
636 destroy();
637 items = other.items;
638 if (otherWasFollowedBySmallVector)
639 {
640 // Other.items should become nullptr, but if it was followed by small vector, restore its link
641 // The Array<> holding the small buffer MUST be placed after the vector.
642 // We should advance by sizeof(Vector<T>) that is sizeof(void*) but on 32 bit we will still have
643 // some padding, as Array<> inherits from SegmentHeader, so it shares the same alignment (that is 8
644 // bytes on all platforms).
645 otherHeader = reinterpret_cast<SegmentHeader*>(reinterpret_cast<char*>(&other) + alignof(SegmentHeader));
646 other.items = otherHeader->getItems<T>();
647 }
648 else
649 {
650 other.items = nullptr;
651 }
652 }
653}
constexpr const T & min(const T &t1, const T &t2)
Finds the minimum of two values.
Definition: Compiler.h:300
constexpr ForwardIterator findIf(ForwardIterator first, ForwardIterator last, UnaryPredicate &&predicate)
Find item satisfying the given predicate.
Definition: AlgorithmFind.h:23
#define SC_ASSERT_DEBUG(e)
Assert expression e to be true.
Definition: Assert.h:82
#define SC_COMPILER_EXPORT
Macro for symbol visibility in non-MSVC compilers.
Definition: Compiler.h:78
#define SC_ASSERT_RELEASE(e)
Assert expression e to be true.
Definition: Assert.h:66
constexpr T && move(T &value)
Converts an lvalue to an rvalue reference.
Definition: Compiler.h:269
unsigned long size_t
Platform independent unsigned size type.
Definition: PrimitiveTypes.h:56
String with compile time configurable inline storage (small string optimization)
Definition: SmallString.h:21
View over a contiguous sequence of items (pointer + size in elements).
Definition: Span.h:20
constexpr const Type * data() const
Returns pointer to first element of the span.
Definition: Span.h:88
constexpr SizeType sizeInElements() const
Size of Span in elements.
Definition: Span.h:104
A non-modifiable owning string with associated encoding.
Definition: String.h:30
IsTriviallyCopyable evaluates to true if the type T can be trivially copied, false otherwise.
Definition: TypeTraits.h:60
A contiguous sequence of heap allocated elements.
Definition: Vector.h:51
bool resizeWithoutInitializing(size_t newSize)
Resizes this vector to newSize, preserving existing elements.
Definition: Vector.h:476
Vector()
Constructs an empty Vector.
Definition: Vector.h:65
bool append(Span< const T > data)
Appends a range of items copying them at the end of vector.
Definition: Vector.h:520
bool pop_back()
Removes the last element of the vector.
Definition: Vector.h:132
Vector(Vector &&other) noexcept
Moves vector contents into this vector.
Definition: Vector.h:388
T & operator[](size_t index) SC_LANGUAGE_LIFETIME_BOUND
Access item at index.
Definition: Vector.h:417
bool push_back(const T &element)
Appends an element copying it at the end of the Vector.
Definition: Vector.h:123
bool push_front(T &&element)
Moves an element in front of the Vector, at position 0.
Definition: Vector.h:118
bool appendMove(U &&src)
Appends another vector moving its contents at the end of vector.
Definition: Vector.h:527
bool removeAll(Lambda &&criteria)
Removes all items matching criteria given by Lambda.
Definition: Vector.h:578
void clear()
Removes all elements from container, calling destructor for each of them.
Definition: Vector.h:483
T * begin() SC_LANGUAGE_LIFETIME_BOUND
Gets pointer to first element of the vector.
Definition: Vector.h:197
bool shrink_to_fit()
Reallocates the vector so that size() == capacity().
Definition: Vector.h:181
bool find(Lambda &&lambda, size_t *foundIndex=nullptr) const
Finds the first item in vector matching criteria given by the lambda.
Definition: Vector.h:571
const T * begin() const SC_LANGUAGE_LIFETIME_BOUND
Gets pointer to first element of the vector.
Definition: Vector.h:200
size_t size() const
Gets size of the vector.
Definition: Vector.h:492
T & back() SC_LANGUAGE_LIFETIME_BOUND
Access the last element of the Vector.
Definition: Vector.h:447
size_t capacity() const
Gets capacity of the vector.
Definition: Vector.h:503
Vector(const Vector &other)
Copies vector into this vector.
Definition: Vector.h:375
Span< T > toSpan() SC_LANGUAGE_LIFETIME_BOUND
Returns a Span wrapping the entire of current vector.
Definition: Vector.h:98
void clearWithoutInitializing()
Sets size() to zero, without calling destructor on elements.
Definition: Vector.h:177
T & front() SC_LANGUAGE_LIFETIME_BOUND
Access the first element of the Vector.
Definition: Vector.h:431
Vector(std::initializer_list< T > list)
Constructs a Vector from an initializer list.
Definition: Vector.h:69
const T & operator[](size_t index) const SC_LANGUAGE_LIFETIME_BOUND
Access item at index.
Definition: Vector.h:424
bool append(Span< const U > src)
Appends a range of items copying them at the end of vector.
Definition: Vector.h:540
bool push_back(T &&element)
Appends an element moving it at the end of the Vector.
Definition: Vector.h:128
~Vector()
Destroys the Vector, releasing allocated memory.
Definition: Vector.h:72
bool remove(const U &value)
Removes all values equal to value
Definition: Vector.h:585
Vector & operator=(Vector &&other)
Move assigns another vector to this one.
Definition: Vector.h:395
bool resize(size_t newSize, const T &value=T())
Resizes this vector to newSize, preserving existing elements.
Definition: Vector.h:469
bool isEmpty() const
Check if the vector is empty.
Definition: Vector.h:185
const T * end() const SC_LANGUAGE_LIFETIME_BOUND
Gets pointer to one after last element of the vector.
Definition: Vector.h:206
bool reserve(size_t newCapacity)
Reserves memory for newCapacity elements, allocating memory if necessary.
Definition: Vector.h:463
const T * data() const SC_LANGUAGE_LIFETIME_BOUND
Gets pointer to first element of the vector.
Definition: Vector.h:212
bool insert(size_t idx, Span< const T > data)
Inserts a range of items copying them at given index.
Definition: Vector.h:514
T * end() SC_LANGUAGE_LIFETIME_BOUND
Gets pointer to one after last element of the vector.
Definition: Vector.h:203
bool removeAt(size_t index)
Removes an item at a given index.
Definition: Vector.h:258
bool push_front(const T &element)
Copies an element in front of the Vector, at position 0.
Definition: Vector.h:113
Vector & operator=(const Vector &other)
Copy assigns another vector to this one.
Definition: Vector.h:405
Span< const T > toSpanConst() const SC_LANGUAGE_LIFETIME_BOUND
Returns a Span wrapping the entire of current vector.
Definition: Vector.h:94
bool contains(const U &value, size_t *foundIndex=nullptr) const
Check if the current vector contains a given value.
Definition: Vector.h:563
bool pop_front()
Removes the first element of the vector.
Definition: Vector.h:136
T * data() SC_LANGUAGE_LIFETIME_BOUND
Gets pointer to first element of the vector.
Definition: Vector.h:209