Sane C++ Libraries
C++ Platform Abstraction Libraries
Vector.h
1// Copyright (c) Stefano Cristiano
2// SPDX-License-Identifier: MIT
3#pragma once
4#include "../Algorithms/AlgorithmRemove.h" // removeIf
5#include "../Foundation/Segment.h"
6#include "../Foundation/Segment.inl"
7#include "../Foundation/TypeTraits.h" // IsTriviallyCopyable
8
9namespace SC
10{
11namespace Internal
12{
13
15template <typename T>
16struct SegmentNonTrivial
17{
18 static void destruct(SegmentHeader& header, size_t bytesOffset, size_t numBytes)
19 {
20 forEach(header, bytesOffset, numBytes, [](auto, T& item) { item.~T(); });
21 }
22 static void copyConstructSingle(SegmentHeader& header, size_t bytesOffset, const T* value, size_t numBytes, size_t)
23 {
24 forEach(header, bytesOffset, numBytes, [value](auto, T& item) { placementNew(item, *value); });
25 }
26 static void copyConstruct(SegmentHeader& dest, size_t bytesOffset, const T* src, size_t numBytes)
27 {
28 forEach(dest, bytesOffset, numBytes, [src](auto idx, T& item) { placementNew(item, src[idx]); });
29 }
30 static void copyAssign(SegmentHeader& dest, size_t bytesOffset, const T* src, size_t numBytes)
31 {
32 forEach(dest, bytesOffset, numBytes, [src](auto idx, T& item) { item = src[idx]; });
33 }
34 static void moveConstruct(SegmentHeader& dest, size_t bytesOffset, T* src, size_t numBytes)
35 {
36 forEach(dest, bytesOffset, numBytes, [src](auto idx, T& item) { placementNew(item, move(src[idx])); });
37 }
38 static void moveAssign(SegmentHeader& dest, size_t bytesOffset, T* src, size_t numBytes)
39 {
40 forEach(dest, bytesOffset, numBytes, [src](auto idx, T& item) { item = move(src[idx]); });
41 }
42
43 static void copyInsert(SegmentHeader& dest, size_t startOffsetBytes, const T* src, size_t numBytes)
44 {
45 T* data = getData(dest, 0);
46
47 const size_t numElements = dest.sizeBytes / sizeof(T);
48 const size_t numToInsert = numBytes / sizeof(T);
49 const size_t insertStartIdx = startOffsetBytes / sizeof(T);
50 const size_t insertEndIdx = insertStartIdx + numToInsert;
51
52 if (insertStartIdx == numElements)
53 {
54 // If the newly inserted elements fall in the uninitialized area, copy construct them
55 for (size_t idx = numElements; idx < numElements + numToInsert; ++idx)
56 {
57 placementNew(data[idx], src[idx - numElements]);
58 }
59 }
60 else
61 {
62 // Move construct some elements in the not initialized area
63 for (size_t idx = numElements; idx < numElements + numToInsert; ++idx)
64 {
65 // Guard against using slots that are before segment start.
66 // In the last loop at end of this scope such slots must be
67 // initialized with placement new instead of assignemnt operator
68 if (idx >= numToInsert)
69 {
70 placementNew(data[idx], move(data[idx - numToInsert]));
71 }
72 }
73
74 // Move assign some elements to slots in "post-move" state left from previous loop
75 for (size_t idx = numElements - 1; idx >= insertStartIdx + numToInsert; --idx)
76 {
77 if (idx >= numToInsert)
78 {
79 data[idx] = move(data[idx - numToInsert]);
80 }
81 }
82
83 // Copy assign source data to slots in "post-move" state left from previous loop
84 for (size_t idx = insertStartIdx; idx < insertEndIdx; ++idx)
85 {
86 // See note in the first loop in this scope to understand use of assignment vs. placement new
87 if (idx < numElements)
88 {
89 data[idx] = src[idx - insertStartIdx];
90 }
91 else
92 {
93 placementNew(data[idx], src[idx - insertStartIdx]);
94 }
95 }
96 }
97 }
98
99 static void remove(SegmentHeader& dest, size_t fromBytesOffset, size_t toBytesOffset)
100 {
101 T* data = getData(dest, 0);
102
103 const size_t numElements = dest.sizeBytes / sizeof(T);
104 const size_t startIdx = fromBytesOffset / sizeof(T);
105 const size_t endIdx = toBytesOffset / sizeof(T);
106 const size_t numToRemove = (endIdx - startIdx);
107
108 for (size_t idx = startIdx; idx < numElements - numToRemove; ++idx)
109 {
110 data[idx] = move(data[idx + numToRemove]);
111 }
112 for (size_t idx = numElements - numToRemove; idx < numElements; ++idx)
113 {
114 data[idx].~T();
115 }
116 }
117
118 private:
119 static T* getData(SegmentHeader& header, size_t byteOffset) { return header.getData<T>() + byteOffset / sizeof(T); }
120
121 static size_t getSize(SegmentHeader& header) { return header.sizeBytes / sizeof(T); }
122
123 template <typename Lambda>
124 static void forEach(SegmentHeader& header, size_t byteOffset, size_t numBytes, Lambda&& lambda)
125 {
126 T* data = getData(header, byteOffset);
127 const size_t numElements = numBytes / sizeof(T);
128 for (size_t idx = 0; idx < numElements; ++idx)
129 {
130 lambda(idx, data[idx]);
131 }
132 }
133};
134
135// Executes operations using trivial or non trivial variations of the move/copy/construct functions.
136// Trivial check cannot be a template param of this class because it would cause Vector to need
137// the entire definition of T to declare Vector<T>, making it impossible to create "recursive" structures
138// like SC::Build::WriteInternal::RenderGroup (that has a "Vector<RenderGroup> children" as member )
139template <typename T>
140struct ObjectVTable
141{
142 using Type = T;
143 static void destruct(SegmentHeader& header, size_t bytesOffset, size_t numBytes)
144 {
145 if SC_LANGUAGE_IF_CONSTEXPR (TypeTraits::IsTriviallyCopyable<T>::value)
146 SegmentTrivial::destruct(header, bytesOffset, numBytes);
147 else
148 SegmentNonTrivial<T>::destruct(header, bytesOffset, numBytes);
149 }
150
151 static void copyConstructSingle(SegmentHeader& header, size_t bytesOffset, const T* value, size_t numBytes,
152 size_t sizeOfValue)
153 {
154 if SC_LANGUAGE_IF_CONSTEXPR (TypeTraits::IsTriviallyCopyable<T>::value)
155 SegmentTrivial::copyConstructSingle(header, bytesOffset, value, numBytes, sizeOfValue);
156 else
157 SegmentNonTrivial<T>::copyConstructSingle(header, bytesOffset, value, numBytes, sizeOfValue);
158 }
159
160 static void copyConstruct(SegmentHeader& dest, size_t bytesOffset, const T* src, size_t numBytes)
161 {
162 if SC_LANGUAGE_IF_CONSTEXPR (TypeTraits::IsTriviallyCopyable<T>::value)
163 SegmentTrivial::copyConstruct(dest, bytesOffset, src, numBytes);
164 else
165 SegmentNonTrivial<T>::copyConstruct(dest, bytesOffset, src, numBytes);
166 }
167
168 static void copyAssign(SegmentHeader& dest, size_t bytesOffset, const T* src, size_t numBytes)
169 {
170 if SC_LANGUAGE_IF_CONSTEXPR (TypeTraits::IsTriviallyCopyable<T>::value)
171 SegmentTrivial::copyAssign(dest, bytesOffset, src, numBytes);
172 else
173 SegmentNonTrivial<T>::copyAssign(dest, bytesOffset, src, numBytes);
174 }
175
176 static void moveConstruct(SegmentHeader& dest, size_t bytesOffset, T* src, size_t numBytes)
177 {
178 if SC_LANGUAGE_IF_CONSTEXPR (TypeTraits::IsTriviallyCopyable<T>::value)
179 SegmentTrivial::moveConstruct(dest, bytesOffset, src, numBytes);
180 else
181 SegmentNonTrivial<T>::moveConstruct(dest, bytesOffset, src, numBytes);
182 }
183
184 static void moveAssign(SegmentHeader& dest, size_t bytesOffset, T* src, size_t numBytes)
185 {
186 if SC_LANGUAGE_IF_CONSTEXPR (TypeTraits::IsTriviallyCopyable<T>::value)
187 SegmentTrivial::moveAssign(dest, bytesOffset, src, numBytes);
188 else
189 SegmentNonTrivial<T>::moveAssign(dest, bytesOffset, src, numBytes);
190 }
191
192 static void copyInsert(SegmentHeader& dest, size_t bytesOffset, const T* src, size_t numBytes)
193 {
194 if SC_LANGUAGE_IF_CONSTEXPR (TypeTraits::IsTriviallyCopyable<T>::value)
195 SegmentTrivial::copyInsert(dest, bytesOffset, src, numBytes);
196 else
197 SegmentNonTrivial<T>::copyInsert(dest, bytesOffset, src, numBytes);
198 }
199
200 static void remove(SegmentHeader& dest, size_t fromBytesOffset, size_t toBytesOffset)
201 {
202 if SC_LANGUAGE_IF_CONSTEXPR (TypeTraits::IsTriviallyCopyable<T>::value)
203 SegmentTrivial::remove(dest, fromBytesOffset, toBytesOffset);
204 else
205 SegmentNonTrivial<T>::remove(dest, fromBytesOffset, toBytesOffset);
206 }
207};
208} // namespace Internal
209
210template <typename T>
211struct SegmentVector : public Internal::ObjectVTable<T>
212{
213 static SegmentHeader* allocateNewHeader(size_t newCapacityInBytes)
214 {
215 return SegmentAllocator::allocateNewHeader(newCapacityInBytes);
216 }
217
218 static SegmentHeader* reallocateExistingHeader(SegmentHeader& src, size_t newCapacityInBytes)
219 {
221 {
222 return SegmentAllocator::reallocateExistingHeader(src, newCapacityInBytes);
223 }
224 else
225 {
226 // TODO: Room for optimization for memcpy-able objects (a >= subset than trivially copyable)
227 SegmentHeader* newHeader = allocateNewHeader(newCapacityInBytes);
228 if (newHeader != nullptr)
229 {
230 *newHeader = src; // copy capacity, size and other fields
231 T* tsrc = src.getData<T>();
232 Internal::ObjectVTable<T>::moveConstruct(*newHeader, 0, tsrc, src.sizeBytes);
233 Internal::ObjectVTable<T>::destruct(src, 0, src.sizeBytes);
234 destroyHeader(src);
235 }
236 return newHeader;
237 }
238 }
239 static void destroyHeader(SegmentHeader& header) { SegmentAllocator::destroyHeader(header); }
240};
243
246
255template <typename T>
256struct Vector : public Segment<SegmentVector<T>>
257{
259
260 // Inherits all constructors from Segment
261 using Parent::Parent;
262
269 template <typename U>
270 [[nodiscard]] bool contains(const U& value, size_t* index = nullptr) const
271 {
272 return Parent::toSpanConst().contains(value, index);
273 }
274
280 template <typename Lambda>
281 [[nodiscard]] bool find(Lambda&& lambda, size_t* index = nullptr) const
282 {
283 return Parent::toSpanConst().find(move(lambda), index);
284 }
285
290 template <typename Lambda>
291 [[nodiscard]] bool removeAll(Lambda&& criteria)
292 {
293 T* itBeg = Parent::begin();
294 T* itEnd = Parent::end();
295 T* it = Algorithms::removeIf(itBeg, itEnd, forward<Lambda>(criteria));
296
297 const size_t numBytes = static_cast<size_t>(itEnd - it) * sizeof(T);
298 const size_t offBytes = static_cast<size_t>(it - itBeg) * sizeof(T);
299 SegmentVector<T>::destruct(*Parent::header, offBytes, numBytes);
300 Parent::header->sizeBytes -= static_cast<decltype(Parent::header->sizeBytes)>(numBytes);
301 return it != itEnd;
302 }
303
308 template <typename U>
309 [[nodiscard]] bool remove(const U& value)
310 {
311 return removeAll([&](auto& item) { return item == value; });
312 }
313};
315
316} // namespace SC
ForwardIterator removeIf(ForwardIterator first, ForwardIterator last, UnaryPredicate &&predicate)
Removes all items in the given range, satisfying the given predicate.
Definition: AlgorithmRemove.h:22
constexpr T && move(T &value)
Converts an lvalue to an rvalue reference.
Definition: Compiler.h:269
Definition: Segment.h:11
A slice of contiguous memory, prefixed by and header containing size and capacity.
Definition: Segment.h:35
Span< const T > toSpanConst() const SC_LANGUAGE_LIFETIME_BOUND
Obtains a Span of internal contents.
Definition: Segment.h:147
Definition: Vector.h:212
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:257
bool contains(const U &value, size_t *index=nullptr) const
Check if the current array contains a given value.
Definition: Vector.h:270
bool removeAll(Lambda &&criteria)
Removes all items matching criteria given by Lambda.
Definition: Vector.h:291
bool remove(const U &value)
Removes all values equal to value
Definition: Vector.h:309
bool find(Lambda &&lambda, size_t *index=nullptr) const
Finds the first item in array matching criteria given by the lambda.
Definition: Vector.h:281