NSUNI/NSLAR Library a250670
Loading...
Searching...
No Matches
static_vector.hpp
Go to the documentation of this file.
1
8#pragma once
9// "PalotasB" Static Vector.
13
14#include <algorithm> // std::for_each, std::move*
15#include <array> // std::array
16#include <iterator> // std::reverse_iterator, std::distance
17#include <memory> // std::uninitialized_*,
18#include <utility> // std::aligned_storage
19
29
30namespace nnl {
31
32namespace utl {
39
50
51template <typename T, std::size_t Capacity> //
52class static_vector {
53 public:
54 // MEMBER TYPES
55
56 // Value type equal to T
57 using value_type = T;
58 // std::size_t without including a header just for this name
59 using size_type = std::size_t;
60 // std::ptrdiff_t without including a header just for this name
61 using difference_type = std::ptrdiff_t;
62 // Reference type is a regular reference, not a proxy
63 using reference = value_type&;
64 using const_reference = const value_type&;
65 // Pointer is a regular pointer
66 using pointer = value_type*;
67 using const_pointer = const value_type*;
68 // Iterator is a regular pointer
69 using iterator = pointer;
70 using const_iterator = const_pointer;
71 // Reverse iterator is what the STL provides for reverse iterating pointers
72 using reverse_iterator = std::reverse_iterator<iterator>;
73 using const_reverse_iterator = std::reverse_iterator<const_iterator>;
74 // The static capacity of the static_vector
75 static const size_type static_capacity = Capacity;
76
77 // CONSTRUCTORS
78
79 // Default constructor
80 // Requires: nothing
81 // Ensures: The static_vector contains zero elements.
82 // Complexity: constant
83 // Exceptions: noexcept
84 static_vector() noexcept : m_size(0) {}
85
86 // "N copies of one value" constructor
87 // Requires:
88 // `count` is less than or equal to `capacity`
89 // Ensures:
90 // The static_vector contains `count` elements copy-constructed from
91 // `value`.
92 // Complexity: O(count)
93 // Exceptions: noexcept iff the copy constructor of value_type is noexcept
94 static_vector(size_type count, const_reference value) //
95 noexcept(noexcept(value_type(value)))
96 : m_size(count) {
97 std::uninitialized_fill(begin(), end(), value);
98 }
99
100 // "N default constructed items" constructor
101 static_vector(size_type count) noexcept(noexcept(value_type{})) : m_size(count) {
102 std::for_each( // C++17 would use std::uninitialized_default_construct
103 storage_begin(), storage_end(), [](storage_type& store) { new (static_cast<void*>(&store)) value_type; });
104 }
105
106 // Initializer list constructor
107 static_vector(std::initializer_list<value_type> init_list) : m_size(init_list.end() - init_list.begin()) {
108 std::uninitialized_copy(init_list.begin(), init_list.end(), begin());
109 }
110
111 // TODO maybe implement trivial copy/move/destruct if `value_type` supports
112 // it
113
114 // Copy constructor
115 static_vector(const static_vector& other) : m_size(other.m_size) {
116 std::uninitialized_copy(other.begin(), other.end(), begin());
117 }
118
119 // Copy assignment
120 static_vector& operator=(const static_vector& other) {
121 if (&other == this) return *this;
122 clear();
123 m_size = other.m_size;
124 std::uninitialized_copy(other.begin(), other.end(), begin());
125 return *this;
126 }
127
128 // Move constructor
129 static_vector(static_vector&& other) : m_size(other.m_size) {
130 std::uninitialized_copy(std::make_move_iterator(other.begin()),
131 std::make_move_iterator(other.end()), //
132 begin());
133 }
134
135 // Move assignment
136 static_vector& operator=(static_vector&& other) {
137 if (&other == this) return *this;
138 clear();
139 m_size = other.m_size;
140 std::uninitialized_copy(std::make_move_iterator(other.begin()), std::make_move_iterator(other.end()), begin());
141 return *this;
142 }
143
144 // Iterator constructor with basic SFINAE mechanism to cancel use with
145 // non-iterator types
146 template <typename Iter, typename = decltype(*std::declval<Iter&>()), typename = decltype(++std::declval<Iter&>())>
147 static_vector(Iter input_begin, Iter input_end) {
148 m_size = std::distance(input_begin, input_end);
149 std::uninitialized_copy(input_begin, input_end, begin());
150 }
151
152 // Destructor
153 // Ensures: all objects are destructed properly, but trivial destructors are
154 // not run.
155 // Complexity: O(size()) for non-trivially destructible value_type,
156 // otherwise constant.
157 ~static_vector() { clear(); }
158
159 // FIXME the equivalent to std::vector::assign functions can be implemented
160 // the same way as the corresponding ctors
161
162 // ELEMENT ACCESS
163
164 // Element access with bounds checking
165 // Requires: no requirement on index, it is checked at runtime
166 // Ensures: element returned or exception thrown without out-of-bounds
167 // memory access
168 // Returns: the element at `index`
169 // Complexity: constant
170 // Exceptions: std::out_of_range
171 reference at(size_type index) {
172 if (index < m_size)
173 return data(index);
174 else
175 NNL_THROW(RangeError(NNL_SRCTAG("")));
176 }
177 const_reference at(size_type index) const {
178 if (index < m_size)
179 return data(index);
180 else
181 NNL_THROW(RangeError(NNL_SRCTAG("")));
182 }
183
184 // Element access with bounds checking
185 // Requires: index is less than size
186 // Returns: the element at `index`
187 // Complexity: constant
188 // Exceptions: noexcept
189 reference operator[](size_t index) noexcept { return data(index); }
190 const_reference operator[](size_t index) const noexcept { return data(index); }
191
192 // Get first element, equivalent to v[0]
193 // Requires: size() > 0
194 reference front() noexcept { return data(0); }
195 const_reference front() const noexcept { return data(0); }
196
197 // Get last element, equivalent to v[v.size() - 1]
198 // Requires: size() > 0
199 reference back() noexcept { return data(m_size - 1); }
200 const_reference back() const noexcept { return data(m_size - 1); }
201
202 // Get underlying data as a raw pointer
203 // Equivalent to &v[0]
204 pointer data() noexcept { return reinterpret_cast<pointer>(&m_data[0]); }
205 const_pointer data() const noexcept { return reinterpret_cast<const_pointer>(&m_data[0]); }
206
207 // ITERATORS
208
209 // Get iterator to the first (`begin`) last (`end`) stored element.
210 // Complexity: constant
211 // Exceptions: noexcept
212 // Returns: iterator to the first element
213 iterator begin() noexcept { return data(); }
214 const_iterator begin() const noexcept { return data(); }
215 const_iterator cbegin() const noexcept { return data(); }
216 // Returns: iterator to one past the last element
217 iterator end() noexcept { return data() + m_size; }
218 const_iterator end() const noexcept { return data() + m_size; }
219 const_iterator cend() const noexcept { return data() + m_size; }
220
221 // Reverse iterators behave the same as their regular pairs except that
222 // `rbegin()` refers to the last element (`end() - 1`) and `rend()` refers
223 // to one past `begin()`
224 // Returns: iterator to the first element in reverse order
225 reverse_iterator rbegin() noexcept { return data() + m_size; }
226 const_reverse_iterator rbegin() const noexcept { return data() + m_size; }
227 const_reverse_iterator crbegin() const noexcept { return data() + m_size; }
228 // Returns: iterator to one past the last element in reverse order
229 reverse_iterator rend() noexcept { return data(); }
230 const_reverse_iterator rend() const noexcept { return data(); }
231 const_reverse_iterator crend() const noexcept { return data(); }
232
233 // CAPACITY
234
235 // These functions all have wide contracts, constant complexity and noexcept
236 // exception specification.
237
238 // The number of elements
239 size_type size() const noexcept { return m_size; }
240
241 // Is it empty?
242 bool empty() const noexcept { return m_size == 0; }
243
244 // Is it full? Note: added in addition to std::vector interface
245 bool full() const noexcept { return m_size == static_capacity; }
246
247 // Max possible size
248 size_type capacity() const noexcept { return static_capacity; }
249
250 // Max possible size
251 size_type max_size() const noexcept { return static_capacity; }
252
253 // reserve intentionally not defined, but it could be a no-op.
254 // shrink_to_fit intentionally not defined, but it could be a no-op.
255
256 // MODIFIERS
257
258 // Clear the vector
259 // Ensures: size() = 0, all objects destructed
260 // Complexity: O(size()) for non-trivially destructible value_type,
261 // otherwise constant.
262 void clear() {
263 if (!std::is_trivially_destructible<value_type>::value)
264 std::for_each(begin(), end(), [](reference r) { r.~value_type(); });
265 m_size = 0;
266 }
267
268 // Insert element at specific position
269 // Requires: valid `pos` iterator, including begin() and end() inclusive.
270 // Ensures: new `value_type` copy_constructed at `pos`
271 // Complexity: exactly `end()` - `pos` moves and one copy
272 iterator insert(const_iterator pos, const value_type& value) {
273 if (full()) NNL_THROW(RangeError(NNL_SRCTAG("")));
274 // Need mutable iterator to change items. Cast is legal in non-const
275 // methos.
276 iterator mut_pos = const_cast<iterator>(pos);
277 // move_backward is recommended when the end of the target range is
278 // outside the input range, last element is moved first
279 std::move_backward(mut_pos, end(), end() + 1);
280 // Construct value, do not assign nonexistent
281 new (mut_pos) value_type(value);
282 m_size++;
283 return mut_pos;
284 }
285 iterator insert(const_iterator pos, value_type&& value) {
286 if (full()) NNL_THROW(RangeError(NNL_SRCTAG("")));
287 // Need mutable iterator to change items. Cast is legal in non-const
288 // methos.
289 iterator mut_pos = const_cast<iterator>(pos);
290 // move_backward is recommended when the end of the target range is
291 // outside the input range, last element is moved first
292 std::move_backward(mut_pos, end(), end() + 1);
293 // Construct value, do not assign nonexistent
294 new (mut_pos) value_type(std::move(value));
295 m_size++;
296 return mut_pos;
297 }
298
299 // Insert `count` copies of `value` at `pos`
300 iterator insert(const_iterator pos, size_type count, const value_type& value) {
301 if (m_size + count < m_size /*ovf*/ || static_capacity < m_size + count) NNL_THROW(RangeError(NNL_SRCTAG("")));
302 // Need mutable iterator to change items. Cast is legal in non-const
303 // methos.
304 iterator mut_pos = const_cast<iterator>(pos);
305 // move_backward is recommended when the end of the target range is
306 // outside the input range, last element is moved first
307 std::move_backward(mut_pos, end(), end() + count);
308 // Construct value, do not assign nonexistent
309 std::for_each(storage_begin() + (mut_pos - begin()), storage_begin() + (mut_pos - begin()) + count,
310 [&](storage_type& store) { new (&store) value_type(value); });
311 m_size += count;
312 return mut_pos;
313 }
314 template <typename InputIter>
315 auto insert(const_iterator pos, InputIter insert_begin, InputIter insert_end)
316 -> std::enable_if_t<
317 std::is_same<typename std::iterator_traits<InputIter>::reference, decltype(*insert_begin)>::value, iterator> {
318 auto count = std::distance(insert_begin, insert_end);
319 if (count < 0 || m_size + static_cast<size_type>(count) < m_size /*ovf*/ ||
320 static_capacity < m_size + static_cast<size_type>(count)) {
321 NNL_THROW(RangeError(NNL_SRCTAG("std::distance(begin, end)")));
322 }
323 // Need mutable iterator to change items. Cast is legal in non-const
324 // methos.
325 iterator mut_pos = const_cast<iterator>(pos);
326 // move_backward is recommended when the end of the target range is
327 // outside the input range, last element is moved first
328 std::move_backward(mut_pos, end(), end() + count);
329 std::for_each(storage_begin() + (mut_pos - begin()), storage_begin() + (mut_pos - begin()) + count,
330 [&](storage_type& store) { new (&store) value_type(*insert_begin++); });
331 m_size += count;
332 return mut_pos;
333 }
334 // TODO insert(const_iterator pos, InputIter begin, InputIter end)
335 // TODO insert(init_list)
336
337 // Inserts new object at `pos` using `args...` as constructor arguments
338 // TODO documentation
339 template <typename... CtorArgs>
340 iterator emplace(const_iterator pos, CtorArgs&&... args) {
341 if (full()) NNL_THROW(RangeError(NNL_SRCTAG("")));
342 // Need mutable iterator to change items. Cast is legal in non-const
343 // methos.
344 iterator mut_pos = const_cast<iterator>(pos);
345 // move_backward is recommended when the end of the target range is
346 // outside the input range, last element is moved first
347 std::move_backward(mut_pos, end(), end() + 1);
348 // Construct value, do not assign nonexistent
349 new (mut_pos) value_type(std::forward<CtorArgs>(args)...);
350 m_size++;
351 return mut_pos;
352 }
353
354 // Erase element at `pos`
355 // TODO docs
356 iterator erase(const_iterator pos) {
357 iterator mut_pos = const_cast<iterator>(pos);
358 mut_pos->~value_type();
359 // move forward, starting from mut_pos and going towards end()
360 std::move(mut_pos + 1, end(), mut_pos);
361 m_size--;
362 return mut_pos;
363 }
364
365 // TODO erase(const_iterator begin, const_iterator end)
366
367 // Add `value` at the end of the list
368 void push_back(const value_type& value) {
369 if (full()) NNL_THROW(RangeError(NNL_SRCTAG("")));
370 new (storage_end()) value_type(value);
371 m_size++;
372 }
373 void push_back(value_type&& value) {
374 if (full()) NNL_THROW(RangeError(NNL_SRCTAG("")));
375 new (storage_end()) value_type(std::move(value));
376 m_size++;
377 }
378
379 // Resize the vector to contain new_size elements
380 // If new_size is less than current size, elements are removed from the end
381 // If new_size is greater than current size, new elements are default-constructed
382 // Requires: new_size <= capacity()
383 // Complexity: O(|new_size - size()|)
384 // Exceptions: std::out_of_range if new_size > capacity()
385 void resize(size_type new_size) {
386 if (new_size > static_capacity) {
387 NNL_THROW(RangeError(NNL_SRCTAG("new_size exceeds capacity")));
388 }
389
390 if (new_size < m_size) {
391 // Shrink: destroy elements from the end
392 if (!std::is_trivially_destructible<value_type>::value) {
393 for (size_type i = new_size; i < m_size; ++i) {
394 data(i).~value_type();
395 }
396 }
397 m_size = new_size;
398 } else if (new_size > m_size) {
399 // Grow: default-construct new elements
400 std::for_each(storage_begin() + m_size, storage_begin() + new_size,
401 [](storage_type& store) { new (static_cast<void*>(&store)) value_type; });
402 m_size = new_size;
403 }
404 // If new_size == m_size, do nothing
405 }
406
407 // Resize the vector to contain new_size elements
408 // If new_size is less than current size, elements are removed from the end
409 // If new_size is greater than current size, new elements are copy-constructed from value
410 // Requires: new_size <= capacity()
411 // Complexity: O(|new_size - size()|)
412 // Exceptions: std::out_of_range if new_size > capacity()
413 void resize(size_type new_size, const value_type& value) {
414 if (new_size > static_capacity) {
415 NNL_THROW(RangeError(NNL_SRCTAG("new_size exceeds capacity")));
416 }
417
418 if (new_size < m_size) {
419 // Shrink: destroy elements from the end
420 if (!std::is_trivially_destructible<value_type>::value) {
421 for (size_type i = new_size; i < m_size; ++i) {
422 data(i).~value_type();
423 }
424 }
425 m_size = new_size;
426 } else if (new_size > m_size) {
427 // Grow: copy-construct new elements from value
428 std::for_each(storage_begin() + m_size, storage_begin() + new_size,
429 [&value](storage_type& store) { new (static_cast<void*>(&store)) value_type(value); });
430 m_size = new_size;
431 }
432 // If new_size == m_size, do nothing
433 }
434
435 // Construct element in-place at the end of the vector
436 // Uses perfect forwarding to pass arguments to the constructor
437 // Requires: size() < capacity()
438 // Returns: reference to the newly constructed element
439 // Complexity: constant
440 // Exceptions: std::out_of_range if vector is full
441 template <typename... Args>
442 reference emplace_back(Args&&... args) {
443 if (full()) NNL_THROW(RangeError(NNL_SRCTAG("size()")));
444
445 // Construct the element in-place at the end of storage
446 pointer new_element = reinterpret_cast<pointer>(&m_data[m_size]);
447 new (new_element) value_type(std::forward<Args>(args)...);
448 m_size++;
449 return *new_element;
450 }
451
452 // TODO pop_back
453
454 // TODO swap
455
456 private:
457 // Use a specific storage type to satisfy alignment requirements
458 using storage_type = std::aligned_storage_t<sizeof(value_type), alignof(value_type)>;
459 // The array providing the inline storage for the elements.
460 std::array<storage_type, static_capacity> m_data = {};
461
462 // The current occupied size of the static_vector
463 size_type m_size = 0;
464
465 // Get data by index, used for convenience instead of (*this)[index]
466 // Note that as opposed to data(), these return a `reference`, not `pointer`
467 reference data(size_t index) noexcept { return *reinterpret_cast<pointer>(&m_data[index]); }
468 const_reference data(size_t index) const noexcept { return *reinterpret_cast<const_pointer>(&m_data[index]); }
469
470 // Get iterators for storage
471 storage_type* storage_begin() noexcept { return &m_data[0]; }
472 storage_type* storage_end() noexcept { return &m_data[m_size]; }
473};
474
475// NON-MEMBER OPERATORS
476
477template <typename T, std::size_t max_size>
478bool operator==(const static_vector<T, max_size>& lhs, const static_vector<T, max_size>& rhs) {
479 if (lhs.size() != rhs.size()) {
480 return false;
481 }
482
483 return std::equal(lhs.begin(), lhs.end(), rhs.begin());
484}
485
486template <typename T, std::size_t max_size>
487bool operator!=(const static_vector<T, max_size>& lhs, const static_vector<T, max_size>& rhs) {
488 return !(lhs == rhs);
489}
490
491// TODO < > <= >=
493} // namespace utl
494} // namespace nnl
Defines the library-wide exception hierarchy and error handling macros.
Exception thrown for out-of-range errors.
Definition exception.hpp:111
#define NNL_THROW(object)
Throws an exception or terminates the program if exceptions are disabled.
Definition exception.hpp:46
std::vector like class with a fixed-size inline storage (aka std::inplace_vector)
Definition static_vector.hpp:52
Definition exception.hpp:56