The Sparta Modeling Framework
Loading...
Searching...
No Matches
FastList.hpp
Go to the documentation of this file.
1// <FastList.hpp> -*- C++ -*-
2
3
11#pragma once
12
13#include <vector>
14#include <limits>
15#include <iostream>
16#include <exception>
17#include <iterator>
18#include <cinttypes>
19#include <cassert>
20#include <cstddef>
21#include <type_traits>
22
25
26namespace sparta::utils
27{
47 template <class DataT>
49 {
50 struct Node
51 {
52 using NodeIdx = int;
53
54 // Where this node is in the vector
55 const NodeIdx index;
56
57 // Points to the next element or the next free
58 // element if this node has been removed.
59 NodeIdx next = -1;
60
61 // Points to the previous element.
62 NodeIdx prev = -1;
63
64 // Stores the memory for an instance of 'T'.
65 // Use placement new to construct the object and
66 // manually invoke its dtor as necessary.
67 alignas(DataT) std::byte type_storage[sizeof(DataT)];
68
69 Node(NodeIdx _index) :
70 index(_index)
71 {}
72 };
73
74 typename Node::NodeIdx advanceNode_(typename Node::NodeIdx node_idx) const {
75 typename Node::NodeIdx ret_idx = (node_idx == -1 ? -1 : nodes_[node_idx].next);
76 return ret_idx;
77 }
78
79 auto getStorage(typename Node::NodeIdx node_idx) {
80 return &nodes_[node_idx].type_storage;
81 }
82
83 auto getStorage(typename Node::NodeIdx node_idx) const {
84 return &nodes_[node_idx].type_storage;
85 }
86
87 public:
88 using value_type = DataT;
89
95 template<bool is_const = true>
96 class NodeIterator : public sparta::utils::IteratorTraits<std::forward_iterator_tag, value_type>
97 {
98 typedef std::conditional_t<is_const, const value_type &, value_type &> RefIteratorType;
99 typedef std::conditional_t<is_const, const value_type *, value_type *> PtrIteratorType;
100 typedef std::conditional_t<is_const, const FastList *, FastList *> FastListPtrType;
101 public:
102
103 NodeIterator() = default;
104
105 NodeIterator(const NodeIterator<false> & iter) :
106 flist_(iter.flist_),
107 node_idx_(iter.node_idx_)
108 {}
109
114 bool isValid() const { return (node_idx_ != -1); }
115
117 PtrIteratorType operator->() {
118 assert(isValid());
119 return reinterpret_cast<PtrIteratorType>(flist_->getStorage(node_idx_));
120 }
121
123 PtrIteratorType operator->() const {
124 assert(isValid());
125 return reinterpret_cast<PtrIteratorType>(flist_->getStorage(node_idx_));
126 }
127
129 RefIteratorType operator* () {
130 assert(isValid());
131 return *reinterpret_cast<PtrIteratorType>(flist_->getStorage(node_idx_));
132 }
133
135 RefIteratorType operator* () const {
136 assert(isValid());
137 return *reinterpret_cast<PtrIteratorType>(flist_->getStorage(node_idx_));
138 }
139
141 int getIndex() const { return node_idx_; }
142
145 {
146 assert(isValid());
147 node_idx_ = flist_->advanceNode_(node_idx_);
148 return *this;
149 }
150
153 {
154 NodeIterator orig = *this;
155 assert(isValid());
156 node_idx_ = flist_->advanceNode_(node_idx_);
157 return orig;
158 }
159
161 bool operator!=(const NodeIterator &rhs)
162 {
163 return (rhs.flist_ != flist_) ||
164 (rhs.node_idx_ != node_idx_);
165 }
166
168 bool operator==(const NodeIterator & node) const noexcept {
169 return (node.flist_ == flist_) && (node.node_idx_ == node_idx_);
170 }
171
173 NodeIterator& operator=(const NodeIterator &rhs) = default;
174 NodeIterator& operator=( NodeIterator &&rhs) = default;
175
176 private:
177 friend class FastList<DataT>;
178
179 NodeIterator(FastListPtrType flist, typename Node::NodeIdx node_idx) :
180 flist_(flist),
181 node_idx_(node_idx)
182 { }
183
184 FastListPtrType flist_ = nullptr;
185 typename Node::NodeIdx node_idx_ = -1;
186 };
187
192 FastList(size_t size) :
194 {}
195
205 FastList(size_t initial_size, size_t max_size) :
206 max_size_(max_size)
207 {
208 sparta_assert(initial_size != 0,
209 "Cannot create a sparta::utils::FastList of size 0");
210 sparta_assert(initial_size <= max_size,
211 "sparta::utils::FastList initial size " << initial_size <<
212 " is larger than its max size " << max_size);
213 // -1 is the end/free sentinel, so the ceiling must fit in NodeIdx.
214 sparta_assert(max_size <= static_cast<size_t>(std::numeric_limits<typename Node::NodeIdx>::max()),
215 "sparta::utils::FastList max size " << max_size <<
216 " exceeds the addressable node index range");
217 int node_idx = 0;
218 nodes_.reserve(max_size_);
219 for(size_t i = 0; i < initial_size; ++i) {
220 Node n(node_idx);
221 n.prev = node_idx - 1;
222 n.next = node_idx + 1;
223 ++node_idx;
224 nodes_.emplace_back(n);
225 }
226 nodes_.back().next = -1;
227 }
228
230 ~FastList() { clear(); }
231
234
237 return iterator(this, first_node_);
238 }
239
242 return const_iterator(this, first_node_);
243 }
244
246 iterator end() { return iterator(this, -1); }
247
249 const_iterator end() const { return const_iterator(this, -1); }
250
252 DataT & front() { return *begin(); }
253
255 const DataT & front() const { return *begin(); }
256
258 bool empty() const { return size_ == 0; }
259
261 size_t size() const { return size_; };
262
264 size_t max_size() const { return max_size_; };
265
267 // Modifiers
268 void clear() noexcept {
269 const auto my_end = end();
270 for(auto it = begin(); it != my_end;) {
271 erase(it++);
272 }
273 }
274
280 {
281 const auto node_idx = entry.getIndex();
282 auto & node_to_erase = nodes_[node_idx];
283 reinterpret_cast<DataT*>(&node_to_erase.type_storage)->~DataT();
284 int next_elem = -1;
285
286 if(first_node_ == node_idx) {
287 first_node_ = node_to_erase.next;
288 }
289 if(last_node_ == node_idx) {
290 last_node_ = node_to_erase.prev;
291 }
292
293 if(SPARTA_EXPECT_FALSE(node_to_erase.next != -1))
294 {
295 auto & next_node = nodes_[node_to_erase.next];
296 next_node.prev = node_to_erase.prev;
297 next_elem = node_to_erase.next;
298 }
299
300 if(SPARTA_EXPECT_FALSE(node_to_erase.prev != -1))
301 {
302 auto & prev_node = nodes_[node_to_erase.prev];
303 prev_node.next = node_to_erase.next;
304 }
305
306 node_to_erase.prev = -1;
307 node_to_erase.next = -1;
308 if(SPARTA_EXPECT_TRUE(free_head_ != -1)) {
309 nodes_[free_head_].prev = node_idx;
310 node_to_erase.next = free_head_;
311 }
312 free_head_ = node_idx;
313 --size_;
314 return iterator(this, next_elem);
315 }
316
317 template<class ...ArgsT>
318 iterator emplace(const const_iterator & pos, ArgsT&&...args)
319 {
320 ensureFreeNode_();
321 const auto index_pos = pos.getIndex();
322
323 // If the index pos is -1, it's either end() or begin() on
324 // an empty list. Just emplace_back (or front, don't matter)
325 if(index_pos == -1) {
326 return emplace_back(std::forward<ArgsT>(args)...);
327 }
328
329 auto & new_node = nodes_[free_head_];
330 free_head_ = new_node.next;
331 new (&new_node.type_storage) DataT(args...);
332 // Update pointers. Start with a clean slate
333 new_node.next = -1;
334 new_node.prev = -1;
335
336 // Insert before the given pt
337 auto & insert_pt = nodes_[index_pos];
338 new_node.next = insert_pt.index;
339 new_node.prev = insert_pt.prev;
340 insert_pt.prev = new_node.index;
341 if(new_node.prev != -1) {
342 // update the previous node's next
343 nodes_[new_node.prev].next = new_node.index;
344 }
345
346 if((first_node_ == index_pos) || (first_node_ == -1))
347 {
348 first_node_ = new_node.index;
349 }
350 ++size_;
351
352 return iterator(this, new_node.index);
353 }
354
360 template<class ...ArgsT>
361 iterator emplace_front(ArgsT&&...args)
362 {
363 ensureFreeNode_();
364
365 auto & new_node = nodes_[free_head_];
366 free_head_ = new_node.next;
367 new (&new_node.type_storage) DataT(args...);
368
369 // Update pointers. Start with a clean slate
370 new_node.next = -1;
371 new_node.prev = -1;
372 if(SPARTA_EXPECT_TRUE(first_node_ != -1))
373 {
374 auto & old_first = nodes_[first_node_];
375 old_first.prev = new_node.index;
376 new_node.next = old_first.index;
377 }
378 first_node_ = new_node.index;
379 if(SPARTA_EXPECT_FALSE(last_node_ == -1)) {
380 last_node_ = first_node_;
381 }
382
383 ++size_;
384 return iterator(this, new_node.index);
385 }
386
392 template<class ...ArgsT>
393 iterator emplace_back(ArgsT&&...args) {
394 ensureFreeNode_();
395
396 auto & new_node = nodes_[free_head_];
397 free_head_ = new_node.next;
398 new (&new_node.type_storage) DataT(args...);
399
400 // Update pointers. Start with a clean slate
401 new_node.next = -1;
402 new_node.prev = -1;
403 if(SPARTA_EXPECT_TRUE(last_node_ != -1))
404 {
405 auto & old_last = nodes_[last_node_];
406 old_last.next = new_node.index;
407 new_node.prev = old_last.index;
408 }
409 last_node_ = new_node.index;
410 if(SPARTA_EXPECT_FALSE(first_node_ == -1)) {
411 first_node_ = last_node_;
412 }
413
414 ++size_;
415 return iterator(this, new_node.index);
416 }
417
420 template<class ...ArgsT>
421 iterator insert(const const_iterator & pos, ArgsT&&...args) {
422 return emplace(pos, args...);
423 }
424
426 void pop_back() {
427 sparta_assert(last_node_ != -1,
428 "Can't pop_back on an empty list");
429 erase(iterator(this, last_node_));
430 }
431
433 void pop_front() {
434 sparta_assert(first_node_ != -1,
435 "Can't pop_front on an empty list");
436 erase(iterator(this, first_node_));
437 }
438
439 private:
440
441 // Friendly printer
442 friend std::ostream & operator<<(std::ostream & os, const FastList<DataT> & fl)
443 {
444 int next_node = fl.first_node_;
445 if(next_node == -1) {
446 os << "<empty>" << std::endl;
447 }
448 else {
449 int index = fl.size_ - 1;
450 do
451 {
452 const auto & n = fl.nodes_[next_node];
453 os << index << " elem=" << *reinterpret_cast<const DataT*>(&n.type_storage)
454 << " n.next=" << n.next
455 << " n.prev=" << n.prev << std::endl;
456 next_node = n.next;
457 --index;
458 } while(next_node != -1);
459 }
460 return os;
461 }
462
464 void ensureFreeNode_() {
465 if(SPARTA_EXPECT_TRUE(free_head_ != -1)) { return; }
466 sparta_assert(nodes_.size() < max_size_,
467 "FastList is out of element room (max_size=" << max_size_ << ")");
468 // Never reallocates: the ctor reserved max_size_.
469 const typename Node::NodeIdx new_idx =
470 static_cast<typename Node::NodeIdx>(nodes_.size());
471 nodes_.emplace_back(new_idx);
472 free_head_ = new_idx;
473 }
474
475 // Stores all the nodes.
476 std::vector<Node> nodes_;
477 const size_t max_size_;
478
479 int free_head_ = 0;
480 int first_node_ = -1;
481 int last_node_ = -1;
482 size_t size_ = 0;
483 };
484}
Defines a few handy (and now deprecated) C++ iterator traits.
Set of macros for Sparta assertions. Caught by the framework.
#define sparta_assert(...)
Simple variadic assertion that will throw a sparta_exception if the condition fails.
#define SPARTA_EXPECT_TRUE(x)
A macro for hinting to the compiler a particular condition should be considered most likely true.
#define SPARTA_EXPECT_FALSE(x)
A macro for hinting to the compiler a particular condition should be considered most likely false.
The internal iterator type of FastList. Use FastList<T>::[const_]iterator instead.
Definition FastList.hpp:97
bool operator!=(const NodeIterator &rhs)
Equality of iterator (not the underlying object)
Definition FastList.hpp:161
bool isValid() const
Determine if the iteartor is valid.
Definition FastList.hpp:114
PtrIteratorType operator->()
Iterator dereference.
Definition FastList.hpp:117
bool operator==(const NodeIterator &node) const noexcept
Equality of iterator (not the underlying object)
Definition FastList.hpp:168
NodeIterator & operator=(const NodeIterator &rhs)=default
Assignments.
NodeIterator operator++(int)
Move to the next iterator (post)
Definition FastList.hpp:152
int getIndex() const
Get the index in the list where this iterator points.
Definition FastList.hpp:141
NodeIterator & operator++()
Move to the next iterator (pre)
Definition FastList.hpp:144
PtrIteratorType operator->() const
Iterator dereference (const)
Definition FastList.hpp:123
RefIteratorType operator*()
Iterator dereference.
Definition FastList.hpp:129
An alternative to std::list, about 70% faster.
Definition FastList.hpp:49
iterator emplace_front(ArgsT &&...args)
Add an element to the front of the list.
Definition FastList.hpp:361
void pop_front()
Pop the first element off of the list.
Definition FastList.hpp:433
~FastList()
Destroy (clear) the list.
Definition FastList.hpp:230
iterator end()
Obtain an end iterator.
Definition FastList.hpp:246
iterator begin()
Obtain a beginning iterator.
Definition FastList.hpp:236
iterator emplace_back(ArgsT &&...args)
emplace an object at the back
Definition FastList.hpp:393
size_t size() const
Definition FastList.hpp:261
iterator insert(const const_iterator &pos, ArgsT &&...args)
Definition FastList.hpp:421
FastList(size_t initial_size, size_t max_size)
Construct a FastList that grows on demand up to a maximum.
Definition FastList.hpp:205
DataT value_type
Handy using.
Definition FastList.hpp:88
const_iterator end() const
Obtain an end const_iterator.
Definition FastList.hpp:249
NodeIterator< true > const_iterator
Iterator type, const.
Definition FastList.hpp:233
NodeIterator< false > iterator
Iterator type.
Definition FastList.hpp:232
iterator erase(const const_iterator &entry)
Erase an element with the given iterator.
Definition FastList.hpp:279
size_t max_size() const
Definition FastList.hpp:264
DataT & front()
Get the front of the fast list non-const.
Definition FastList.hpp:252
FastList(size_t size)
Construct FastList of a given size.
Definition FastList.hpp:192
const_iterator begin() const
Obtain a beginning const_iterator.
Definition FastList.hpp:241
const DataT & front() const
Get the front of the fast list, const.
Definition FastList.hpp:255
void pop_back()
Pop the last element off of the list.
Definition FastList.hpp:426