Flow
Documentation for the Flow C++ Library
Loading...
Searching...
No Matches
flow_vector.h
Go to the documentation of this file.
1#pragma once
5#include <cassert>
6#include <concepts>
7#include <initializer_list>
8#include <memory>
9#include <utility>
10
11namespace flow {
12
13 template <typename Strategy>
15 std::default_initializable<Strategy> &&
16 noexcept(Strategy()) &&
17 requires(Strategy strategy, std::size_t num) {
18 { strategy(num) } -> std::same_as<std::size_t>;
19 };
20
22 struct GoldenExpand {
23 std::size_t operator()(std::size_t oldCapacity) const {
24 return oldCapacity + oldCapacity / 2 + 1;
25 }
26 };
27
28 struct DoubleExpand {
29 std::size_t operator()(std::size_t oldCapacity) const {
30 return oldCapacity * 2 + 1;
31 }
32 };
33
35 std::size_t n1 = 0;
36 std::size_t n2 = 1;
37 std::size_t operator()(std::size_t oldCapacity) {
38 do {
39 std::size_t n3 = n1 + n2;
40 n1 = n2;
41 n2 = n3;
42 } while (n2 <= oldCapacity);
43 return n2;
44 }
45 };
46 };
47
48 template <typename T, typename Allocator = PolymorphicAllocator<>, GrowthStrategy Strategy = VectorGrowthStrategy::GoldenExpand>
49 class Vector {
50 public:
51 using value_type = T;
52 using reference = T&;
53 using const_reference = const T&;
54 using pointer = T*;
55 using const_pointer = const T*;
56 using iterator = T*;
57 using const_iterator = const T*;
58 using size_type = std::size_t;
59 using difference_type = std::ptrdiff_t;
60 using allocator_type = std::allocator_traits<Allocator>::template rebind_alloc<T>;
61
62 private:
63 using allocator_trait = std::allocator_traits<Allocator>::template rebind_traits<T>;
64
67 std::size_t size_;
68 std::size_t capacity_;
70
71 public:
73 constexpr Vector() noexcept
75 }
76
79 explicit constexpr Vector(const allocator_type& allocator)
80 noexcept(noexcept(allocator_type(allocator)))
81 : allocator_(allocator),
83 size_(0),
84 capacity_(0),
85 buffer_(nullptr) {
86 }
87
90 constexpr Vector(const Vector& rhs)
91 : Vector(rhs, rhs.get_allocator()) {
92 }
93
97 constexpr Vector(const Vector& rhs, const allocator_type& allocator)
98 : allocator_(allocator),
100 size_(rhs.size_),
101 capacity_(rhs.capacity_),
104 }
105
108 constexpr Vector(Vector&& rhs) noexcept
109 : Vector(std::move(rhs), rhs.get_allocator()) {
110 }
111
115 constexpr Vector(Vector&& rhs, const allocator_type& allocator)
116 : Vector(allocator) {
117
118 // If allocator is different, then do not use the provided allocator
119 // to manage the memory of an existed vector.
120 if (rhs.get_allocator() == allocator) {
121 swap(*this, rhs);
122 } else {
123 // Create a copy with new allocator, then swap over.
124 Vector rhsCopy(rhs, allocator);
125 swap(*this, rhsCopy);
126 }
127 }
128
134 template <std::input_iterator It>
135 explicit constexpr Vector(It first, It last, const allocator_type& allocator = {})
136 : allocator_(allocator),
138 size_(std::distance(first, last)),
142 }
143
147 constexpr Vector(std::initializer_list<T> list, const allocator_type& allocator = {})
148 : Vector(list.begin(), list.end(), allocator) {
149 }
150
154 explicit constexpr Vector(std::size_t count, const allocator_type& allocator = {})
155 : allocator_(allocator),
157 size_(count),
158 capacity_(count),
159 buffer_(allocator_trait::allocate(allocator_, count)) {
161 }
162
167 explicit constexpr Vector(std::size_t count, const T& value, const allocator_type& allocator = {})
168 : allocator_(allocator),
170 size_(count),
171 capacity_(count),
172 buffer_(allocator_trait::allocate(allocator_, count)) {
173 uninitializedFillN(allocator_, buffer_, count, value);
174 }
175
176 // Destructor.
180
181 // Operator.
183 noexcept(std::is_nothrow_swappable_v<Vector>) {
184 swap(*this, rhs);
185 return *this;
186 }
187
188 T& operator[](std::size_t i) noexcept {
189 assert(i < size_ && "index out of bound");
190 return buffer_[i];
191 }
192
193 const T& operator[](std::size_t i) const noexcept {
194 assert(i < size_ && "index out of bound");
195 return buffer_[i];
196 }
197
200 allocator_type get_allocator() const noexcept {
201 return allocator_;
202 }
203
204 // Iterators
205 iterator begin() noexcept {
206 return buffer_;
207 }
208
209 constexpr const_iterator begin() const noexcept {
210 return buffer_;
211 }
212
213 iterator end() noexcept {
214 return buffer_ + size_;
215 }
216
217 constexpr const_iterator end() const noexcept {
218 return buffer_ + size_;
219 }
220
223 constexpr std::size_t size() const noexcept {
224 return size_;
225 }
226
229 constexpr std::size_t capacity() const noexcept {
230 return capacity_;
231 }
232
235 constexpr bool empty() const noexcept {
236 return size_ == 0;
237 }
238
241 T& front() noexcept {
242 assert(0 < size_ && "access empty Vector front");
243 return buffer_[0];
244 }
245
248 constexpr const T& front() const noexcept {
249 assert(0 < size_ && "access empty Vector front");
250 return buffer_[0];
251 }
252
255 T& back() noexcept {
256 assert(0 < size_ && "access empty Vector back");
257 return buffer_[size_ - 1];
258 }
259
262 constexpr const T& back() const noexcept {
263 assert(0 < size_ && "access empty Vector back");
264 return buffer_[size_ - 1];
265 }
266
268 void clear() noexcept {
270 size_ = 0;
271 }
272
275 void reserve(std::size_t capacity) {
276 if (capacity_ < capacity) {
278 }
279 }
280
283 void resize(std::size_t size) {
285 }
286
290 void resize(std::size_t size, const T& value) {
291 resizeImp(size, value);
292 }
293
296 void pushBack(const T& value) {
297 // https://stackoverflow.com/questions/10890653/why-would-i-ever-use-push-back-instead-of-emplace-back
298 // TLDR: Consider emplace back an address when constructing unique_ptr.
299 emplaceBack(value);
300 }
301
304 void pushBack(T&& value) {
305 emplaceBack(std::move(value));
306 }
307
310 template <typename ...Args>
311 void emplaceBack(Args&&... args) {
312 if (size_ == capacity_) {
314 }
315 allocator_trait::construct(allocator_, buffer_ + size_, std::forward<Args>(args)...);
316 ++size_;
317 }
318
320 void popBack() noexcept {
321 assert(0 < size_ && "can not pop back from an empty vector");
322 --size_;
323 allocator_trait::destroy(allocator_, buffer_ + size_);
324 }
325
329 iterator erase(iterator pos) noexcept {
330 assert(begin() <= pos && pos < end() && "can not erase before begin() or at the end()");
331 std::move(pos + 1, end(), pos);
332 popBack();
333 return pos;
334 }
335
340 iterator erase(iterator first, iterator last) noexcept {
341 assert(begin() <= first && last <= end() && "can not erase before begin() or past the end()");
342 assert(first <= last && "first must be before last");
343
344 iterator afterMoved = std::move(last, end(), first);
345 destroyElements(allocator_, afterMoved, end());
346 size_ -= last - first;
347 return first;
348 }
349
353 template <typename ...Args>
354 void emplace(iterator pos, Args&&... args) {
355 // Special case: append at the end.
356 if (pos == end()) {
357 emplaceBack(std::forward<Args>(args)...);
358 return;
359 }
360
361 if (size_ < capacity_) {
362 // Enough capacity, right shift the elements.
363 // Major optimization to use memcpy, copy_backward, or range move_backward instead of handroll loop. A 70% reduction in computation time.
364 allocator_trait::construct(allocator_, end(), std::move(back()));
365 std::move_backward(pos, end() - 1, end());
366 *pos = T(std::forward<Args>(args)...);
367
368 } else {
369 // Not enough capacity, relocate all to a new buffer.
370 std::size_t index = pos - buffer_;
372 allocator_trait::construct(allocator_, buffer_ + index, std::forward<Args>(args)...);
373 }
374 ++size_;
375 }
376
380 void insert(iterator pos, const T& value) {
381 emplace(pos, value);
382 }
383
387 void insert(iterator pos, T&& value) {
388 emplace(pos, std::move(value));
389 }
390
395 void insert(iterator pos, std::size_t count, const T& value) {
396 insert(pos, CountedValueViewIterator(value, count), CountedValueViewIterator(value));
397 }
398
405 template <std::input_iterator It>
406 void insert(iterator pos, It first, It last) {
407 const std::size_t insertedElementsSize = std::distance(first, last);
408 const std::size_t requiredSize = size_ + insertedElementsSize;
409
410 if (capacity_ < requiredSize) {
411 // Not enough capacity, relocate to a new buffer.
412 std::size_t index = pos - begin();
413 std::size_t newCapacity = growthStrategy_(requiredSize);
414 relocateBufferWithHoles(newCapacity, pos, insertedElementsSize);
415 uninitializedForward(allocator_, first, last, begin() + index);
416 } else {
417 // Enough capacity, shift elements.
418 // Inserted that are inbound = Shifted that are outbound.
419 const std::size_t shiftedElementSize = end() - pos;
420 const std::size_t conflictedRangeSize = std::min(insertedElementsSize, shiftedElementSize);
421 const std::size_t shiftedElementsInboundSize = shiftedElementSize - conflictedRangeSize;
422 const std::size_t insertedElementsOutboundSize = insertedElementsSize - conflictedRangeSize;
423
424 // Shifted elements that are on uninitialized buffer range.
425 // This range can be empty if insert at end().
426 uninitializedMove(allocator_, end() - conflictedRangeSize, end(), pos + insertedElementsSize + shiftedElementsInboundSize);
427
428 // Shifted elements that are on initialized buffer range.
429 // This range can be empty if all shifted elements must move to the uninitialized buffer from the previous step.
430 std::move_backward(pos, pos + shiftedElementsInboundSize, end());
431
432 // Inserted elements that are on initialized buffer range starting at pos.
433 // This range can be empty if insert at end().
434 for (std::size_t i = 0; i < conflictedRangeSize; ++i, ++first, ++pos) {
435 *pos = *first;
436 }
437
438 // Inserted elements that are on uninitialized buffer range.
439 // This range can be empty if insertedElementSize <= shiftedElementSize -> conflictedRangeSize = insertedElementSize.
440 // Such that there are enough space in the initialized buffer range.
441 uninitializedForwardN(allocator_, first, insertedElementsOutboundSize, pos);
442 }
443
444 size_ = requiredSize;
445 }
446
450 void insert(iterator pos, std::initializer_list<T> list) {
451 insert(pos, list.begin(), list.end());
452 }
453
454 private:
455 // Clean up the old buffer.
456 void updateBuffer(T* buffer, std::size_t capacity) {
458 buffer_ = buffer;
460 }
461
462 // Allocate a new buffer with the capacity, and relocate the elements to it.
463 // This update the buffer_ and capacity_ internally.
464 void relocateBuffer(std::size_t capacity) {
465 assert(capacity_ < capacity && "new capacity is no larger than the old capacity");
466
467 T* buffer = allocator_trait::allocate(allocator_, capacity);
468 uninitializedMove(allocator_, begin(), end(), buffer);
469 updateBuffer(buffer, capacity);
470 }
471
472 // Allocate a new buffer with the capacity, and relocate the elements to it with a hole at pos.
473 void relocateBufferWithHoles(std::size_t capacity, iterator pos, std::size_t holeSize) {
474 assert(capacity_ < capacity && "new capacity is no larger than the old capacity");
475
476 T* buffer = allocator_trait::allocate(allocator_, capacity);
477 iterator leftHalf = uninitializedMove(allocator_, begin(), pos, buffer); // Move left half.
478 uninitializedMove(allocator_, pos, end(), leftHalf + holeSize); // Move right half.
479 updateBuffer(buffer, capacity);
480 }
481
482 template <typename ...U>
483 void resizeImp(std::size_t size, const U&... optionalValue) {
484 static_assert(sizeof...(optionalValue) <= 1, "no fill value or exactly one copy");
485 if (size_ < size) {
486 // Relocate if not enough capacity.
487 if (capacity_ < size) {
489 }
490 uninitializedEmplace(allocator_, buffer_ + size_, buffer_ + size, optionalValue...);
491
492 } else if (size < size_) {
493 // Shrink.
495 }
496 size_ = size;
497 }
498
499 // Friends.
500 friend void swap(Vector& lhs, Vector& rhs) noexcept {
501 using std::swap;
502 swap(lhs.allocator_, rhs.allocator_);
503 swap(lhs.growthStrategy_, rhs.growthStrategy_);
504 swap(lhs.size_, rhs.size_);
505 swap(lhs.capacity_, rhs.capacity_);
506 swap(lhs.buffer_, rhs.buffer_);
507 }
508 };
509}
510
511template <typename T>
512bool operator==(const flow::Vector<T>& lhs, const flow::Vector<T>& rhs) noexcept {
513 return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
514}
515
516template <typename T>
517bool operator!=(const flow::Vector<T>& lhs, const flow::Vector<T>& rhs) noexcept {
518 return !(lhs == rhs);
519}
520
521//namespace nope {
522// template <typename T>
523// class Vector {
524// template<typename MappingFn, template<typename> typename Alloc = Allocator>
525// Vector<std::invoke_result_t<MappingFn, const T&>, Alloc> map(const MappingFn& fn) const {
526// Vector<std::invoke_result_t<MappingFn, const T&>, Alloc> mapped;
527// mapped.relocate(size_);
528// for (const T& val : *this) {
529// mapped.push_back(fn(val));
530// }
531// return mapped;
532// }
533//
534// template <typename FilterFn, template<typename> typename Alloc = Allocator>
535// Vector<T, Alloc> filter(const FilterFn& fn) const {
536// static_assert(std::is_same_v<std::invoke_result_t<FilterFn, const T&>, bool>, "filter function must evaluate to bool");
537// Vector<T, Alloc> filtered;
538// for (const T& val : *this) {
539// if (fn(val)) {
540// filtered.push_back(val);
541// }
542// }
543// return filtered;
544// }
545//
546// template <typename CallbackFn>
547// void for_each(const CallbackFn& fn) const {
548// for (auto& val : *this) {
549// fn(val);
550// }
551// }
552//
553// friend class Vector;
554// };
555//
556//
557//
558// //TODO: support move semantics
559// template <typename ...Vec>
560// Vector<Tuple<typename Vec::value_type...>> zip(const Vec&... vec) {
561// using ZipType = Tuple<typename Vec::value_type...>;
562// std::size_t minSize = std::min({vec.size()...});
563// Vector<ZipType> zipped;
564// zipped.reserve(minSize);
565// for (std::size_t i = 0; i < minSize; ++i) {
566// zipped.push_back(ZipType{vec[i]...});
567// }
568// return zipped;
569// }
570//
571// Vector<std::string> split(const std::string& line, const std::string& delimiter) {
572// if (delimiter.empty()) {
573// return { line };
574// }
575//
576// Vector<std::string> tokens;
577// for (std::size_t pos = 0;;) {
578// std::size_t nextPos = line.find(delimiter, pos);
579// tokens.push_back(line.substr(pos, nextPos - pos));
580// if (nextPos == std::string::npos) {
581// break;
582// }
583// pos = nextPos + delimiter.size();
584// }
585// return tokens;
586// }
587//
588// std::string join(const Vector<std::string>& tokens, const std::string& separator) {
589// if (tokens.empty()) {
590// return "";
591// }
592//
593// std::size_t sz = (tokens.size() - 1) * separator.size();
594// for (auto& token : tokens) {
595// sz += token.size();
596// }
597//
598// std::string line;
599// line.reserve(sz);
600// for (std::size_t i = 0; i + 1 < tokens.size(); ++i) {
601// line += tokens[i] + separator;
602// }
603// line += tokens.back();
604// return line;
605// }
606//}
Iterator that returns a constant value a fixed number of times. Useful for creating a virtual range o...
void insert(iterator pos, It first, It last)
Inserts a range of elements at the specified position. Does not support self-insertion.
allocator_type get_allocator() const noexcept
void emplaceBack(Args &&... args)
Constructs an element in-place at the end of the vector.
constexpr std::size_t size() const noexcept
Returns the number of elements in the vector.
constexpr Vector(Vector &&rhs) noexcept
Move-constructs a vector with the same allocator as the source.
iterator erase(iterator first, iterator last) noexcept
Removes elements in the range [first, last).
constexpr const T & back() const noexcept
Returns a reference to the last element.
constexpr Vector(Vector &&rhs, const allocator_type &allocator)
Move-constructs a vector with a custom allocator.
std::allocator_traits< Allocator >::template rebind_traits< T > allocator_trait
Definition flow_vector.h:63
void pushBack(const T &value)
Adds an element to the end of the vector.
iterator begin() noexcept
void relocateBufferWithHoles(std::size_t capacity, iterator pos, std::size_t holeSize)
constexpr Vector(const Vector &rhs)
Copy-constructs a vector using the same allocator as the source.
Definition flow_vector.h:90
constexpr std::size_t capacity() const noexcept
Returns the total allocated capacity.
VectorGrowthStrategy::GoldenExpand growthStrategy_
Definition flow_vector.h:66
void emplace(iterator pos, Args &&... args)
Constructs an element in-place at the specified iterator position.
constexpr Vector(std::size_t count, const allocator_type &allocator={})
Constructs a vector with count default-initialized elements.
iterator erase(iterator pos) noexcept
Removes an element at the specified iterator position.
void pushBack(T &&value)
Adds an element to the end of the vector.
void insert(iterator pos, T &&value)
Inserts a moved element at the specified iterator position.
T & operator[](std::size_t i) noexcept
constexpr Vector(std::size_t count, const T &value, const allocator_type &allocator={})
Constructs a vector with count copies of a given value.
void resize(std::size_t size, const T &value)
Resizes the vector and fills new elements with a given value.
constexpr Vector(const allocator_type &allocator) noexcept(noexcept(allocator_type(allocator)))
Constructs an empty vector with a specific allocator.
Definition flow_vector.h:79
void resizeImp(std::size_t size, const U &... optionalValue)
const T & operator[](std::size_t i) const noexcept
void relocateBuffer(std::size_t capacity)
void updateBuffer(T *buffer, std::size_t capacity)
constexpr const T & front() const noexcept
Returns a reference to the first element.
constexpr Vector(It first, It last, const allocator_type &allocator={})
Constructs a vector from an iterator range.
T & front() noexcept
Returns a reference to the first element.
void resize(std::size_t size)
Resizes the vector to contain the given number of default constructed elements.
void insert(iterator pos, std::size_t count, const T &value)
Inserts multiple copies of a value at the specified position.
void insert(iterator pos, std::initializer_list< T > list)
Inserts an initializer list of elements at the specified position.
friend void swap(Vector &lhs, Vector &rhs) noexcept
T & back() noexcept
Returns a reference to the last element.
Vector & operator=(Vector rhs) noexcept(std::is_nothrow_swappable_v< Vector >)
constexpr bool empty() const noexcept
Checks whether the vector is empty.
constexpr const_iterator end() const noexcept
std::ptrdiff_t difference_type
Definition flow_vector.h:59
std::allocator_traits< Allocator >::template rebind_alloc< T > allocator_type
Definition flow_vector.h:60
iterator end() noexcept
constexpr Vector(std::initializer_list< T > list, const allocator_type &allocator={})
Constructs a vector from an initializer list.
void popBack() noexcept
Removes the last element of the vector.
void clear() noexcept
Clears the contents of the vector.
constexpr Vector(const Vector &rhs, const allocator_type &allocator)
Copy-constructs a vector with a custom allocator.
Definition flow_vector.h:97
constexpr Vector() noexcept
Constructs an empty vector with default allocator and growth strategy.
Definition flow_vector.h:73
constexpr const_iterator begin() const noexcept
void reserve(std::size_t capacity)
Ensures the vector can hold at least the given capacity without reallocating.
void insert(iterator pos, const T &value)
Inserts a copy of an element at the specified iterator position.
bool operator!=(const flow::Vector< T > &lhs, const flow::Vector< T > &rhs) noexcept
bool operator==(const flow::Vector< T > &lhs, const flow::Vector< T > &rhs) noexcept
OutputIt uninitializedEmplace(AllocatorType &allocator, OutputIt first, OutputIt last, const Args &... args)
Constructs objects in uninitialized memory by copying arguments to their constructor....
void destroyElements(AllocatorType &allocator, InputIt first, InputIt last) noexcept
Destroys a range of constructed objects in memory.
void destroyElementsN(AllocatorType &allocator, InputIt first, std::size_t count) noexcept
Destroys count objects in a range.
OutputIt uninitializedMove(AllocatorType &allocator, InputIt first, InputIt last, OutputIt dest) noexcept
Moves elements from a source range to uninitialized memory.
OutputIt uninitializedFillN(AllocatorType &allocator, OutputIt first, std::size_t count, const T &value)
Fills count elements in uninitialized memory with a value.
OutputIt uninitializedForwardN(AllocatorType &allocator, InputIt first, std::size_t count, OutputIt dest)
Forward count elements from a source range to uninitialized memory.
OutputIt uninitializedForward(AllocatorType &allocator, InputIt first, InputIt last, OutputIt dest)
Forward elements from a source range to uninitialized memory.
void deleteBuffer(AllocatorType &allocator, T *buffer, std::size_t size, std::size_t capacity) noexcept
Destroys and deallocates the buffer.
OutputIt uninitializedEmplaceN(AllocatorType &allocator, OutputIt first, std::size_t count, const Args &... args)
Constructs a specified number of objects in uninitialized memory by copying constructor arguments....
std::size_t operator()(std::size_t oldCapacity) const
Definition flow_vector.h:29
std::size_t operator()(std::size_t oldCapacity)
Definition flow_vector.h:37
std::size_t operator()(std::size_t oldCapacity) const
Definition flow_vector.h:23