Pyrogenesis  trunk
StaticVector.h
Go to the documentation of this file.
1 /* Copyright (C) 2023 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #ifndef INCLUDED_PS_STATICVECTOR
19 #define INCLUDED_PS_STATICVECTOR
20 
21 #include <algorithm>
22 #include <array>
23 #include <cstdint>
24 #include <fmt/format.h>
25 #include <initializer_list>
26 #include <limits>
27 #include <memory>
28 #include <new>
29 #include <stdexcept>
30 
31 namespace PS
32 {
33 
34 struct CapacityExceededException : public std::length_error
35 {
36  using std::length_error::length_error;
37 };
38 
39 template<size_t N>
41 {
42  if constexpr (N <= std::numeric_limits<uint_fast8_t>::max())
43  return static_cast<uint_fast8_t>(0);
44  else if constexpr (N <= std::numeric_limits<uint_fast16_t>::max())
45  return static_cast<uint_fast16_t>(0);
46  else if constexpr (N <= std::numeric_limits<uint_fast32_t>::max())
47  return static_cast<uint_fast32_t>(0);
48  else if constexpr (N <= std::numeric_limits<uint_fast64_t>::max())
49  return static_cast<uint_fast64_t>(0);
50  else
51  {
52  static_assert(N <= std::numeric_limits<uintmax_t>::max());
53  return static_cast<uintmax_t>(0);
54  }
55 }
56 
57 template<size_t N>
58 constexpr auto MakeSmallestCapableSigned()
59 {
60  // TODO C++20: Use std::cmp_*
61  if constexpr (N <= static_cast<uintmax_t>(std::numeric_limits<int_fast8_t>::max()) &&
62  -static_cast<intmax_t>(N) >= std::numeric_limits<int_fast8_t>::min())
63  return static_cast<int_fast8_t>(0);
64  else if constexpr (N <= static_cast<uintmax_t>(std::numeric_limits<int_fast16_t>::max()) &&
65  -static_cast<intmax_t>(N) >= std::numeric_limits<int_fast16_t>::min())
66  return static_cast<int_fast16_t>(0);
67  else if constexpr (N <= static_cast<uintmax_t>(std::numeric_limits<int_fast32_t>::max()) &&
68  -static_cast<intmax_t>(N) >= std::numeric_limits<int_fast32_t>::min())
69  return static_cast<int_fast32_t>(0);
70  else if constexpr (N <= static_cast<uintmax_t>(std::numeric_limits<int_fast64_t>::max()) &&
71  -static_cast<intmax_t>(N) >= std::numeric_limits<int_fast64_t>::min())
72  return static_cast<int_fast64_t>(0);
73  else
74  {
75  static_assert(N <= static_cast<uintmax_t>(std::numeric_limits<intmax_t>::max()) &&
76  -static_cast<intmax_t>(N) >= std::numeric_limits<intmax_t>::min());
77  return static_cast<intmax_t>(0);
78  }
79 }
80 
81 /**
82  * A conntainer close to std::vector but the elements are stored in place:
83  * There is a fixed capacity and there is no dynamic memory allocation.
84  * Note: moving a StaticVector will be slower than moving a std::vector in
85  * case of sizeof(StaticVector) > sizeof(std::vector).
86  */
87 template<typename T, size_t N>
89 {
90 public:
91  static_assert(std::is_nothrow_destructible_v<T>);
92 
93  using value_type = T;
94  using size_type = decltype(MakeSmallestCapableUnsigned<N>());
95  using difference_type = decltype(MakeSmallestCapableSigned<N>());
97  using const_reference = const value_type&;
98  using pointer = value_type*;
99  using const_pointer = const value_type*;
100  using iterator = pointer;
102  using reverse_iterator = std::reverse_iterator<iterator>;
103  using const_reverse_iterator = std::reverse_iterator<const_iterator>;
104 
105 
106  StaticVector() = default;
107  StaticVector(const StaticVector& other) noexcept(std::is_nothrow_copy_constructible_v<T>)
108  : m_Size{other.size()}
109  {
110  std::uninitialized_copy(other.begin(), other.end(), begin());
111  }
112 
113  template<size_t OtherN>
114  explicit StaticVector(const StaticVector<T, OtherN>& other) noexcept(
115  std::is_nothrow_copy_constructible_v<T>)
116  : m_Size{other.size()}
117  {
118  static_assert(OtherN < N);
119 
120  std::uninitialized_copy(other.begin(), other.end(), begin());
121  }
122 
123  StaticVector& operator=(const StaticVector& other) noexcept(std::is_nothrow_copy_constructible_v<T>
124  && std::is_nothrow_copy_assignable_v<T>)
125  {
126  const size_type initializedCopies{std::min(other.size(), size())};
127  std::copy_n(other.begin(), initializedCopies, begin());
128  std::uninitialized_copy(other.begin() + initializedCopies, other.end(),
129  begin() + initializedCopies);
130  std::destroy(begin() + initializedCopies, end());
131 
132  m_Size = other.size();
133  return *this;
134  }
135 
136  template<size_t OtherN>
138  std::is_nothrow_copy_constructible_v<T> && std::is_nothrow_copy_assignable_v<T>)
139  {
140  static_assert(OtherN < N);
141 
142  const size_type initializedCopies{std::min(other.size(), size())};
143  std::copy_n(other.begin(), initializedCopies, begin());
144  std::uninitialized_copy(other.begin() + initializedCopies, other.end(),
145  begin() + initializedCopies);
146  std::destroy(begin() + initializedCopies, end());
147 
148  m_Size = other.size();
149  return *this;
150  }
151 
152  StaticVector(StaticVector&& other) noexcept(std::is_nothrow_move_constructible_v<T>)
153  : m_Size{other.size()}
154  {
155  std::uninitialized_move(other.begin(), other.end(), begin());
156  }
157 
158  template<size_t OtherN>
160  noexcept(std::is_nothrow_move_constructible_v<T>)
161  : m_Size{other.size()}
162  {
163  static_assert(OtherN < N);
164 
165  std::uninitialized_move(other.begin(), other.end(), begin());
166  }
167 
168  StaticVector& operator=(StaticVector&& other) noexcept(std::is_nothrow_move_constructible_v<T> &&
169  std::is_nothrow_move_assignable_v<T>)
170  {
171  const size_type initializedMoves{std::min(other.size(), size())};
172  std::move(other.begin(), other.begin() + initializedMoves, begin());
173  std::uninitialized_move(other.begin() + initializedMoves, other.end(),
174  begin() + initializedMoves);
175  std::destroy(begin() + initializedMoves, end());
176 
177  m_Size = other.size();
178  return *this;
179  }
180 
181  template<size_t OtherN>
183  std::is_nothrow_move_constructible_v<T> && std::is_nothrow_move_assignable_v<T>)
184  {
185  static_assert(OtherN < N);
186 
187  const size_type initializedMoves{std::min(other.size(), size())};
188  std::move(other.begin(), other.begin() + initializedMoves, begin());
189  std::uninitialized_move(other.begin() + initializedMoves, other.end(),
190  begin() + initializedMoves);
191  std::destroy(begin() + initializedMoves, end());
192 
193  m_Size = other.size();
194  return *this;
195  }
196 
198  {
199  clear();
200  }
201 
202  StaticVector(const size_type count, const T& value)
203  : m_Size{count}
204  {
205  if (count > N)
206  throw CapacityExceededException{fmt::format(
207  "Tried to construct a StaticVector with a size of {} but the capacity is only {}",
208  count, N)};
209 
210  std::uninitialized_fill(begin(), end(), value);
211  }
212 
213  StaticVector(const size_type count)
214  : m_Size{count}
215  {
216  if (count > N)
217  throw CapacityExceededException{fmt::format(
218  "Tried to construct a StaticVector with a size of {} but the capacity is only {}",
219  count, N)};
220 
221  std::uninitialized_default_construct(begin(), end());
222  }
223 
224  StaticVector(const std::initializer_list<T> init)
225  : m_Size{static_cast<size_type>(init.size())} // Will be tested below.
226  {
227  if (init.size() > N)
228  throw CapacityExceededException{fmt::format(
229  "Tried to construct a StaticVector with a size of {} but the capacity is only {}",
230  init.size(), N)};
231 
232  std::uninitialized_copy(init.begin(), init.end(), begin());
233  }
234 
235  StaticVector& operator=(const std::initializer_list<T> init)
236  {
237  if (init.size() > N)
238  throw CapacityExceededException{fmt::format(
239  "Tried to construct a StaticVector with a size of {} but the capacity is only {}",
240  init.size(), N)};
241 
242  clear();
243  std::uninitialized_copy(init.begin(), init.end(), begin());
244  m_Size = init.size();
245  }
246 
247 
248 
249  reference at(const size_type index)
250  {
251  if (index >= m_Size)
252  throw std::out_of_range{fmt::format("Called at({}) but there are only {} elements.",
253  index, size())};
254 
255  return (*this)[index];
256  }
257 
258  const_reference at(const size_type index) const
259  {
260  if (index >= size())
261  throw std::out_of_range{fmt::format("Called at({}) but there are only {} elements.",
262  index, size())};
263 
264  return (*this)[index];
265  }
266 
267  reference operator[](const size_type index) noexcept
268  {
269  ASSERT(index < size());
270  return *(begin() + index);
271  }
272 
273  const_reference operator[](const size_type index) const noexcept
274  {
275  ASSERT(index < size());
276  return *(begin() + index);
277  }
278 
279  reference front() noexcept
280  {
281  ASSERT(!empty());
282  return *begin();
283  }
284 
285  const_reference front() const noexcept
286  {
287  ASSERT(!empty());
288  return *begin();
289  }
290 
291  reference back() noexcept
292  {
293  ASSERT(!empty());
294  return *std::prev(end());
295  }
296 
297  const_reference back() const noexcept
298  {
299  ASSERT(!empty());
300  return *std::prev(end());
301  }
302 
303  pointer data() noexcept
304  {
305  return std::launder(reinterpret_cast<pointer>(m_Data.data()));
306  }
307 
308  const_pointer data() const noexcept
309  {
310  return std::launder(reinterpret_cast<const_pointer>(m_Data.data()));
311  }
312 
313 
314  iterator begin() noexcept
315  {
316  return data();
317  }
318 
319  const_iterator begin() const noexcept
320  {
321  return cbegin();
322  }
323 
324  const_iterator cbegin() const noexcept
325  {
326  return data();
327  }
328 
329  iterator end() noexcept
330  {
331  return begin() + size();
332  }
333 
334  const_iterator end() const noexcept
335  {
336  return cend();
337  }
338 
339  const_iterator cend() const noexcept
340  {
341  return cbegin() + size();
342  }
343 
345  {
346  return std::make_reverse_iterator(end());
347  }
348 
350  {
351  return crbegin();
352  }
353 
355  {
356  return std::make_reverse_iterator(end());
357  }
358 
360  {
361  return std::make_reverse_iterator(begin());
362  }
363 
364  const_reverse_iterator rend() const noexcept
365  {
366  return crend();
367  }
368 
369  const_reverse_iterator crend() const noexcept
370  {
371  return std::make_reverse_iterator(cbegin());
372  }
373 
374  bool empty() const noexcept
375  {
376  return size() == 0;
377  }
378 
379  bool full() const noexcept
380  {
381  return size() == N;
382  }
383 
384  size_type size() const noexcept
385  {
386  return m_Size;
387  }
388 
389  constexpr size_type capacity() const noexcept
390  {
391  return N;
392  }
393 
394 
395  void clear() noexcept
396  {
397  std::destroy(begin(), end());
398  m_Size = 0;
399  }
400 
401  /**
402  * Inserts an element at location. The elements which were in the range
403  * [ location, end() ) get moved no the next position.
404  *
405  * Exceptions:
406  * If an exception is thrown when inserting an element at the end this
407  * function has no effect (strong exception guarantee).
408  * Otherwise the program is in a valid state (Basic exception guarantee).
409  */
410  iterator insert(const const_iterator location, const T& value)
411  {
412  if (full())
413  throw CapacityExceededException{"Called insert but the StaticVector is already full"};
414 
415  if (location == end())
416  return std::addressof(emplace_back(value));
417 
418  new(end()) T{std::move(back())};
419  ++m_Size;
420 
421  const iterator mutableLocation{MutableIter(location)};
422  std::move_backward(mutableLocation, std::prev(end(), 2), std::prev(end(), 1));
423 
424  *mutableLocation = value;
425  return mutableLocation;
426  }
427 
428  /**
429  * Same as above but the new element is move-constructed.
430  *
431  * If an exception is thrown when inserting an element at the end this
432  * function has no effect (strong exception guarantee).
433  * If an exception is thrown the program is in a valid state
434  * (Basic exception guarantee).
435  */
436  iterator insert(const const_iterator location, T&& value)
437  {
438  if (full())
439  throw CapacityExceededException{"Called insert but the StaticVector is already full"};
440 
441  if (location == end())
442  return std::addressof(emplace_back(std::move(value)));
443 
444  const iterator mutableLocation{MakeMutableIterator(location)};
445  new(end()) T{std::move(back())};
446  ++m_Size;
447 
448  std::move_backward(mutableLocation, end() - 2, end() -1);
449 
450  *mutableLocation = std::move(value);
451  return mutableLocation;
452  }
453 
454  /**
455  * If an exception is thrown this function has no effect
456  * (strong exception guarantee).
457  */
458  void push_back(const T& value)
459  {
460  emplace_back(value);
461  }
462 
463  /**
464  * If an exception is thrown this function has no effect
465  * (strong exception guarantee).
466  */
467  void push_back(T&& value)
468  {
469  emplace_back(std::move(value));
470  }
471 
472  /**
473  * If an exception is thrown this function has no effect
474  * (strong exception guarantee).
475  */
476  template<typename... Args>
477  reference emplace_back(Args&&... args)
478  {
479  if (full())
481  "Called emplace_back but the StaticVector is already full"};
482 
483  const iterator location{begin() + size()};
484  new(location) T{std::forward<Args>(args)...};
485  ++m_Size;
486  return *location;
487  }
488 
489  void pop_back() noexcept
490  {
491  ASSERT(!empty());
492  std::destroy_at(std::addressof(back()));
493  --m_Size;
494  }
495 
496  /**
497  * Constructs or destructs elements to adjust to newSize. After this call
498  * the StaticVector contains newSize elements. Unlike std::vector the
499  * capacity does not get changed. If newSize is bigger then the capacity
500  * a CapacityExceededException is thrown.
501  *
502  * If newSize is smaller than size() (shrinking) no exception is thrown
503  * (Nothrow exception guarantee).
504  * If an exception is thrown this function has no effect.
505  * (strong exception guarantee)
506  */
507  void resize(const size_type newSize)
508  {
509  if (newSize > N)
510  throw CapacityExceededException{fmt::format(
511  "Can not resize StaticVector to {} the capacity is {}", newSize, N)};
512 
513  if (newSize > size())
514  std::uninitialized_default_construct(end(), begin() + newSize);
515  else
516  std::destroy(begin() + newSize, end());
517 
518  m_Size = newSize;
519  }
520 
521  /**
522  * Same as above but uses value to copy-construct the new elements.
523  *
524  * If newSize is smaller than size() (shrinking) no exception is thrown
525  * (Nothrow exception guarantee).
526  * If an exception is thrown this function has no effect.
527  * (strong exception guarantee)
528  */
529  void resize(const size_type newSize, const T& value)
530  {
531  if (newSize > N)
532  throw CapacityExceededException{fmt::format(
533  "Can't resize the StaticVector to {} the capacity is {}", newSize, N)};
534 
535  if (newSize > size())
536  std::uninitialized_fill(end(), begin() + newSize, value);
537  else
538  std::destroy(begin() + newSize, end());
539 
540  m_Size = newSize;
541  }
542 
543  template<size_t OtherN>
544  friend bool operator==(const StaticVector<T, N>& lhs, const StaticVector<T, OtherN>& rhs)
545  {
546  return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
547  }
548 
549  template<size_t OtherN>
550  friend bool operator!=(const StaticVector<T, N>& lhs, const StaticVector<T, OtherN>& rhs)
551  {
552  return !(lhs == rhs);
553  }
554 
555 private:
557  {
558  return begin() + (iter - begin());
559  }
560 
561  using EagerInitialized = std::array<T, N>;
562  alignas(EagerInitialized) std::array<std::byte, sizeof(T) * N> m_Data;
563  size_type m_Size{0};
564 };
565 
566 } // namespace PS
567 
568 #endif // INCLUDED_PS_STATICVECTOR
decltype(MakeSmallestCapableSigned< N >()) difference_type
Definition: StaticVector.h:95
const_pointer data() const noexcept
Definition: StaticVector.h:308
reference operator[](const size_type index) noexcept
Definition: StaticVector.h:267
const_reverse_iterator crbegin() const noexcept
Definition: StaticVector.h:354
const_iterator cbegin() const noexcept
Definition: StaticVector.h:324
StaticVector(const size_type count, const T &value)
Definition: StaticVector.h:202
iterator insert(const const_iterator location, T &&value)
Same as above but the new element is move-constructed.
Definition: StaticVector.h:436
Definition: StaticVector.h:34
pointer data() noexcept
Definition: StaticVector.h:303
StaticVector & operator=(const StaticVector< T, OtherN > &other) noexcept(std::is_nothrow_copy_constructible_v< T > &&std::is_nothrow_copy_assignable_v< T >)
Definition: StaticVector.h:137
reference emplace_back(Args &&... args)
If an exception is thrown this function has no effect (strong exception guarantee).
Definition: StaticVector.h:477
reverse_iterator rend() noexcept
Definition: StaticVector.h:359
const_iterator begin() const noexcept
Definition: StaticVector.h:319
const_reverse_iterator rend() const noexcept
Definition: StaticVector.h:364
StaticVector(const StaticVector< T, OtherN > &other) noexcept(std::is_nothrow_copy_constructible_v< T >)
Definition: StaticVector.h:114
~StaticVector()
Definition: StaticVector.h:197
constexpr auto MakeSmallestCapableSigned()
Definition: StaticVector.h:58
bool empty() const noexcept
Definition: StaticVector.h:374
#define ASSERT(expr)
same as ENSURE in debug mode, does nothing in release mode.
Definition: debug.h:318
StaticVector & operator=(StaticVector< T, OtherN > &&other) noexcept(std::is_nothrow_move_constructible_v< T > &&std::is_nothrow_move_assignable_v< T >)
Definition: StaticVector.h:182
const_reverse_iterator crend() const noexcept
Definition: StaticVector.h:369
const_reference front() const noexcept
Definition: StaticVector.h:285
bool full() const noexcept
Definition: StaticVector.h:379
const_iterator cend() const noexcept
Definition: StaticVector.h:339
iterator MakeMutableIterator(const const_iterator iter) noexcept
Definition: StaticVector.h:556
const_iterator end() const noexcept
Definition: StaticVector.h:334
void push_back(T &&value)
If an exception is thrown this function has no effect (strong exception guarantee).
Definition: StaticVector.h:467
std::reverse_iterator< iterator > reverse_iterator
Definition: StaticVector.h:102
void pop_back() noexcept
Definition: StaticVector.h:489
constexpr size_type capacity() const noexcept
Definition: StaticVector.h:389
constexpr auto MakeSmallestCapableUnsigned()
Definition: StaticVector.h:40
StaticVector(const size_type count)
Definition: StaticVector.h:213
const_reverse_iterator rbegin() const noexcept
Definition: StaticVector.h:349
StaticVector & operator=(StaticVector &&other) noexcept(std::is_nothrow_move_constructible_v< T > &&std::is_nothrow_move_assignable_v< T >)
Definition: StaticVector.h:168
decltype(MakeSmallestCapableUnsigned< N >()) size_type
Definition: StaticVector.h:94
const_reference operator[](const size_type index) const noexcept
Definition: StaticVector.h:273
Represents a texture object.
Definition: TextureManager.h:256
void clear() noexcept
Definition: StaticVector.h:395
iterator begin() noexcept
Definition: StaticVector.h:314
#define T(string_literal)
Definition: secure_crt.cpp:77
size_type size() const noexcept
Definition: StaticVector.h:384
const_reference back() const noexcept
Definition: StaticVector.h:297
void push_back(const T &value)
If an exception is thrown this function has no effect (strong exception guarantee).
Definition: StaticVector.h:458
reference back() noexcept
Definition: StaticVector.h:291
StaticVector & operator=(const StaticVector &other) noexcept(std::is_nothrow_copy_constructible_v< T > &&std::is_nothrow_copy_assignable_v< T >)
Definition: StaticVector.h:123
void resize(const size_type newSize)
Constructs or destructs elements to adjust to newSize.
Definition: StaticVector.h:507
friend bool operator!=(const StaticVector< T, N > &lhs, const StaticVector< T, OtherN > &rhs)
Definition: StaticVector.h:550
A conntainer close to std::vector but the elements are stored in place: There is a fixed capacity and...
Definition: StaticVector.h:88
Definition: Span.h:25
StaticVector & operator=(const std::initializer_list< T > init)
Definition: StaticVector.h:235
friend bool operator==(const StaticVector< T, N > &lhs, const StaticVector< T, OtherN > &rhs)
Definition: StaticVector.h:544
iterator end() noexcept
Definition: StaticVector.h:329
StaticVector(StaticVector &&other) noexcept(std::is_nothrow_move_constructible_v< T >)
Definition: StaticVector.h:152
std::reverse_iterator< const_iterator > const_reverse_iterator
Definition: StaticVector.h:103
void resize(const size_type newSize, const T &value)
Same as above but uses value to copy-construct the new elements.
Definition: StaticVector.h:529
std::array< CTexture *, N > EagerInitialized
Definition: StaticVector.h:561
reverse_iterator rbegin() noexcept
Definition: StaticVector.h:344
reference at(const size_type index)
Definition: StaticVector.h:249
std::array< std::byte, sizeof(T) *N > m_Data
Definition: StaticVector.h:562
StaticVector(const std::initializer_list< T > init)
Definition: StaticVector.h:224
StaticVector(const StaticVector &other) noexcept(std::is_nothrow_copy_constructible_v< T >)
Definition: StaticVector.h:107
StaticVector(StaticVector< T, OtherN > &&other) noexcept(std::is_nothrow_move_constructible_v< T >)
Definition: StaticVector.h:159
iterator insert(const const_iterator location, const T &value)
Inserts an element at location.
Definition: StaticVector.h:410
reference front() noexcept
Definition: StaticVector.h:279
const_reference at(const size_type index) const
Definition: StaticVector.h:258