dispenso 1.6.2
A library for task parallelism
Loading...
Searching...
No Matches
small_vector.h
Go to the documentation of this file.
1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
22#pragma once
23
24#include <cassert>
25#include <cstddef>
26#include <initializer_list>
27#include <new>
28#include <utility>
29
30#include <dispenso/platform.h>
31
32namespace dispenso {
33
49template <typename T, size_t N = 4>
51 static_assert(N > 0, "SmallVector requires at least 1 inline element. Use std::vector for N=0.");
52 static_assert(N < 65536, "SmallVector inline capacity is too large. Use std::vector instead.");
53
54 public:
55 using value_type = T;
56 using size_type = size_t;
57 using difference_type = std::ptrdiff_t;
58 using reference = T&;
59 using const_reference = const T&;
60 using pointer = T*;
61 using const_pointer = const T*;
62 using iterator = T*;
63 using const_iterator = const T*;
64
68 SmallVector() noexcept : size_(0) {}
69
75 explicit SmallVector(size_type count) : size_(0) {
76 resize(count);
77 }
78
85 SmallVector(size_type count, const T& value) : size_(0) {
86 resize(count, value);
87 }
88
94 SmallVector(std::initializer_list<T> init) : size_(0) {
95 ensureCapacity(init.size());
96 for (const auto& v : init) {
97 emplace_back(v);
98 }
99 }
100
102 SmallVector(const SmallVector& other) : size_(0) {
103 ensureCapacity(other.rawSize());
104 for (const auto& v : other) {
105 emplace_back(v);
106 }
107 }
108
110 SmallVector(SmallVector&& other) noexcept : size_(0) {
111 if (other.isInline()) {
112 // Inline means the heap bit is clear, so size_ is already the count, and
113 // hoisting it keeps the stores below from forcing a reload each pass.
114 const size_type n = other.size_;
115 for (size_type i = 0; i < n; ++i) {
116 new (inlineData() + i) T(std::move(other.inlineData()[i]));
117 other.inlineData()[i].~T();
118 }
119 } else {
120 storage_.heap_.ptr = other.storage_.heap_.ptr;
121 storage_.heap_.capacity = other.storage_.heap_.capacity;
122 }
123 // Count and heap bit both transfer verbatim.
124 size_ = other.size_;
125 other.size_ = 0;
126 }
127
128 ~SmallVector() {
129 destroyAll();
130 }
131
133 SmallVector& operator=(const SmallVector& other) {
134 if (this != &other) {
135 destroyAll();
136 size_ = 0;
137 ensureCapacity(other.rawSize());
138 for (const auto& v : other) {
139 emplace_back(v);
140 }
141 }
142 return *this;
143 }
144
146 SmallVector& operator=(SmallVector&& other) noexcept {
147 if (this != &other) {
148 destroyAll();
149
150 if (other.isInline()) {
151 // Inline means the heap bit is clear, so size_ is already the count,
152 // and hoisting it keeps the stores below from forcing a reload each
153 // pass.
154 const size_type n = other.size_;
155 for (size_type i = 0; i < n; ++i) {
156 new (inlineData() + i) T(std::move(other.inlineData()[i]));
157 other.inlineData()[i].~T();
158 }
159 } else {
160 storage_.heap_.ptr = other.storage_.heap_.ptr;
161 storage_.heap_.capacity = other.storage_.heap_.capacity;
162 }
163 // Count and heap bit both transfer verbatim. Nothing between destroyAll()
164 // and here reads size_: the inline branch goes through inlineData()
165 // rather than data(), and the heap branch overwrites the freed pointer
166 // before anything can observe it.
167 size_ = other.size_;
168 other.size_ = 0;
169 }
170 return *this;
171 }
172
173 // --- Element Access ---
174
176 reference operator[](size_type pos) {
177 return data()[pos];
178 }
180 const_reference operator[](size_type pos) const {
181 return data()[pos];
182 }
184 reference front() {
185 return data()[0];
186 }
188 const_reference front() const {
189 return data()[0];
190 }
192 reference back() {
193 return data()[rawSize() - 1];
194 }
196 const_reference back() const {
197 return data()[rawSize() - 1];
198 }
199
201 pointer data() noexcept {
202 return isInline() ? inlineData() : storage_.heap_.ptr;
203 }
205 const_pointer data() const noexcept {
206 return isInline() ? inlineData() : storage_.heap_.ptr;
207 }
208
209 // --- Iterators ---
210
212 iterator begin() noexcept {
213 return data();
214 }
216 const_iterator begin() const noexcept {
217 return data();
218 }
220 const_iterator cbegin() const noexcept {
221 return data();
222 }
224 iterator end() noexcept {
225 return data() + rawSize();
226 }
228 const_iterator end() const noexcept {
229 return data() + rawSize();
230 }
232 const_iterator cend() const noexcept {
233 return data() + rawSize();
234 }
235
236 // --- Capacity ---
237
239 bool empty() const noexcept {
240 return rawSize() == 0;
241 }
243 size_type size() const noexcept {
244 return rawSize();
245 }
246
251 size_type capacity() const noexcept {
252 return isInline() ? N : storage_.heap_.capacity;
253 }
254
260 void reserve(size_type newCap) {
261 ensureCapacity(newCap);
262 }
263
264 // --- Modifiers ---
265
270 void clear() noexcept {
271 destroyAll();
272 size_ = 0;
273 }
274
276 void push_back(const T& value) {
277 emplace_back(value);
278 }
280 void push_back(T&& value) {
281 emplace_back(std::move(value));
282 }
283
290 template <typename... Args>
291 reference emplace_back(Args&&... args) {
292 T* ptr;
293 if (isInline()) {
294 size_type sz = rawSize();
295 if (sz < N) {
296 ptr = inlineData();
297 } else {
298 growToHeap(N * 2);
299 ptr = storage_.heap_.ptr;
300 }
301 } else {
302 size_type sz = rawSize();
303 if (sz == storage_.heap_.capacity) {
304 growToHeap(storage_.heap_.capacity * 2);
305 }
306 ptr = storage_.heap_.ptr;
307 }
308 size_type idx = rawSize();
309 new (ptr + idx) T(std::forward<Args>(args)...);
310 // Increment preserves heap bit naturally
311 ++size_;
312 assert(rawSize() > 0 && "Size overflow into heap bit");
313 return ptr[idx];
314 }
315
317 void pop_back() {
318 T* ptr = data();
319 size_type sz = rawSize();
320 ptr[sz - 1].~T();
321 // Decrement preserves heap bit naturally
322 --size_;
323 }
324
330 void resize(size_type count) {
331 size_type sz = rawSize();
332 if (count > sz) {
333 ensureCapacity(count);
334 T* ptr = data();
335 for (size_type i = sz; i < count; ++i) {
336 new (ptr + i) T();
337 }
338 setSize(count);
339 } else if (count < sz) {
340 T* ptr = data();
341 for (size_type i = count; i < sz; ++i) {
342 ptr[i].~T();
343 }
344 setSize(count);
345 }
346 }
347
355 void resize(size_type count, const T& value) {
356 size_type sz = rawSize();
357 if (count > sz) {
358 ensureCapacity(count);
359 T* ptr = data();
360 for (size_type i = sz; i < count; ++i) {
361 new (ptr + i) T(value);
362 }
363 setSize(count);
364 } else if (count < sz) {
365 T* ptr = data();
366 for (size_type i = count; i < sz; ++i) {
367 ptr[i].~T();
368 }
369 setSize(count);
370 }
371 }
372
381 iterator erase(const_iterator pos) {
382 T* ptr = data();
383 size_type sz = rawSize();
384 size_type index = pos - ptr;
385
386 for (size_type i = index; i + 1 < sz; ++i) {
387 ptr[i] = std::move(ptr[i + 1]);
388 }
389 ptr[sz - 1].~T();
390 --size_; // Preserves heap bit
391 return data() + index;
392 }
393
394 private:
395 // High bit of size_ tracks heap vs inline mode.
396 // Since kHeapBit >> N, comparisons like size_ <= N and size_ < N
397 // are naturally false when the heap bit is set, so isInline() and
398 // many internal checks work without masking.
399 static constexpr size_type kHeapBit = size_type(1) << (sizeof(size_type) * 8 - 1);
400 static constexpr size_type kSizeMask = ~kHeapBit;
401
402 bool isInline() const noexcept {
403 return (size_ & kHeapBit) == 0;
404 }
405
406 size_type rawSize() const noexcept {
407 return size_ & kSizeMask;
408 }
409
410 // Set the size portion, preserving the heap bit.
411 void setSize(size_type s) noexcept {
412 assert((s & kHeapBit) == 0 && "Size overflow into heap bit");
413 size_ = (size_ & kHeapBit) | s;
414 }
415
416 T* inlineData() noexcept {
417 return reinterpret_cast<T*>(&storage_.inline_);
418 }
419 const T* inlineData() const noexcept {
420 return reinterpret_cast<const T*>(&storage_.inline_);
421 }
422
423 void destroyAll() noexcept {
424 T* ptr = data();
425 size_type sz = rawSize();
426 for (size_type i = 0; i < sz; ++i) {
427 ptr[i].~T();
428 }
429 if (!isInline()) {
430 // GCC on 32-bit ARM reports this as freeing the inline buffer. It is a
431 // false positive: the heap bit lives inside size_ and the buffer is a
432 // union member aliasing heap_.ptr, and across the inlined move paths GCC
433 // stops being able to prove the guard above excludes inline storage. The
434 // same code is clean under GCC on x86_64, x86-32, aarch64, riscv32 and
435 // riscv64, which is not what a genuine logic error would look like.
436 // Simplifying the move paths to a single size_ assignment did not change
437 // its mind either.
438 DISPENSO_DISABLE_WARNING_PUSH
439 DISPENSO_DISABLE_WARNING_FREE_NONHEAP_OBJECT
440 ::operator delete(storage_.heap_.ptr);
441 DISPENSO_DISABLE_WARNING_POP
442 }
443 }
444
445 // Grow to heap storage with the specified capacity.
446 // Moves existing elements, frees old heap if applicable, sets heap bit.
447 void growToHeap(size_type newCap) {
448 T* newData = static_cast<T*>(::operator new(newCap * sizeof(T)));
449 T* oldData = data();
450 size_type sz = rawSize();
451
452 for (size_type i = 0; i < sz; ++i) {
453 new (newData + i) T(std::move(oldData[i]));
454 oldData[i].~T();
455 }
456
457 if (!isInline()) {
458 ::operator delete(storage_.heap_.ptr);
459 }
460
461 storage_.heap_.ptr = newData;
462 storage_.heap_.capacity = newCap;
463 size_ = kHeapBit | sz;
464 }
465
466 void ensureCapacity(size_type newCap) {
467 if (newCap <= N && isInline()) {
468 return;
469 }
470 if (isInline()) {
471 growToHeap(newCap);
472 } else if (newCap > storage_.heap_.capacity) {
473 growToHeap(newCap);
474 }
475 }
476
477 size_type size_;
478
479 struct HeapStorage {
480 T* ptr;
481 size_type capacity;
482 };
483
484 union Storage {
485 alignas(T) unsigned char inline_[sizeof(T) * N];
486 HeapStorage heap_;
487 Storage() noexcept {}
488 ~Storage() {}
489 } storage_;
490};
491
492} // namespace dispenso
pointer data() noexcept
void reserve(size_type newCap)
SmallVector(const SmallVector &other)
void push_back(const T &value)
void resize(size_type count)
void push_back(T &&value)
const_iterator end() const noexcept
const_iterator cbegin() const noexcept
const_reference back() const
const_reference front() const
SmallVector(SmallVector &&other) noexcept
bool empty() const noexcept
SmallVector(std::initializer_list< T > init)
const_iterator begin() const noexcept
const_pointer data() const noexcept
size_type capacity() const noexcept
void resize(size_type count, const T &value)
reference emplace_back(Args &&... args)
const_iterator cend() const noexcept
iterator end() noexcept
SmallVector(size_type count, const T &value)
iterator erase(const_iterator pos)
size_type size() const noexcept
SmallVector(size_type count)
iterator begin() noexcept
void clear() noexcept