SpECTRE Documentation Coverage Report
Current view: top level - DataStructures - StaticDeque.hpp Hit Total Coverage
Commit: 9f349d3c09e1c03107f00c2135ca40e209d3b84c Lines: 1 57 1.8 %
Date: 2023-06-09 21:05:06
Legend: Lines: hit not hit

          Line data    Source code
       1           0 : // Distributed under the MIT License.
       2             : // See LICENSE.txt for details.
       3             : 
       4             : #pragma once
       5             : 
       6             : #include <algorithm>
       7             : #include <cstddef>
       8             : #include <iterator>
       9             : #include <pup.h>
      10             : #include <type_traits>
      11             : 
      12             : #include "Utilities/PrintHelpers.hpp"
      13             : #include "Utilities/Requires.hpp"
      14             : #include "Utilities/StlBoilerplate.hpp"
      15             : 
      16             : namespace static_deque_detail {
      17             : // "The extent to which an implementation determines that a type
      18             : // cannot be an input iterator is unspecified, except that as a
      19             : // minimum integral types shall not qualify as input iterators."
      20             : //   -- N4659 26.2.1.17
      21             : template <typename U>
      22             : constexpr static bool is_input_iterator = not std::is_integral_v<U>;
      23             : }  // namespace static_deque_detail
      24             : 
      25             : /// A class implementing the std::deque interface with static storage.
      26             : ///
      27             : /// Differences from std::deque:
      28             : /// * The size of the container cannot be increased above \p Capacity.
      29             : /// * Operations moving the entire container are \f$O(n)\f$ instead of
      30             : ///   \f$O(1)\f$ in the size and invalidate all references and
      31             : ///   iterators.
      32             : /// * Erasing elements from the front of the queue invalidates all
      33             : ///   iterators (but not references).
      34             : ///
      35             : /// This last point is not a fundamental limitation, but could be
      36             : /// corrected with a more complicated iterator implementation if the
      37             : /// standard behavior is found to be useful.
      38             : template <typename T, size_t Capacity>
      39           1 : class StaticDeque
      40             :     : public stl_boilerplate::RandomAccessSequence<StaticDeque<T, Capacity>,
      41             :                                                    T, true> {
      42             :  public:
      43             :   // Aliases needed in the class definition.  The rest can just be
      44             :   // inherited.
      45           0 :   using size_type = typename StaticDeque::size_type;
      46           0 :   using difference_type = typename StaticDeque::difference_type;
      47           0 :   using iterator = typename StaticDeque::iterator;
      48           0 :   using const_iterator = typename StaticDeque::const_iterator;
      49             : 
      50           0 :   StaticDeque() = default;
      51           0 :   explicit StaticDeque(size_type count);
      52           0 :   StaticDeque(size_type n, const T& value);
      53             :   template <
      54             :       typename InputIterator,
      55             :       Requires<static_deque_detail::is_input_iterator<InputIterator>> = nullptr>
      56           0 :   StaticDeque(InputIterator first, InputIterator last);
      57           0 :   StaticDeque(const StaticDeque& other);
      58           0 :   StaticDeque(StaticDeque&& other);
      59           0 :   StaticDeque(std::initializer_list<T> init);
      60             : 
      61           0 :   ~StaticDeque();
      62             : 
      63           0 :   StaticDeque& operator=(const StaticDeque& other);
      64           0 :   StaticDeque& operator=(StaticDeque&& other);
      65           0 :   StaticDeque& operator=(std::initializer_list<T> init);
      66             : 
      67             :   template <
      68             :       typename InputIterator,
      69             :       Requires<static_deque_detail::is_input_iterator<InputIterator>> = nullptr>
      70           0 :   void assign(InputIterator first, InputIterator last);
      71           0 :   void assign(size_type count, const T& value);
      72           0 :   void assign(std::initializer_list<T> init);
      73             : 
      74           0 :   size_type size() const { return size_; }
      75           0 :   size_type max_size() const { return Capacity; }
      76           0 :   void resize(size_type count);
      77           0 :   void resize(size_type count, const T& value);
      78           0 :   void shrink_to_fit() {}
      79             : 
      80           0 :   T& operator[](size_type n) { return *entry_address(n); }
      81           0 :   const T& operator[](size_type n) const { return *entry_address(n); }
      82             : 
      83             :   template <typename... Args>
      84           0 :   T& emplace_front(Args&&... args);
      85             :   template <typename... Args>
      86           0 :   T& emplace_back(Args&&... args);
      87             :   template <typename... Args>
      88           0 :   iterator emplace(const_iterator position, Args&&... args);
      89             : 
      90           0 :   void push_front(const T& x) { emplace_front(x); }
      91           0 :   void push_front(T&& x) { emplace_front(std::move(x)); }
      92           0 :   void push_back(const T& x) { emplace_back(x); }
      93           0 :   void push_back(T&& x) { emplace_back(std::move(x)); }
      94             : 
      95           0 :   iterator insert(const_iterator position, const T& x);
      96           0 :   iterator insert(const_iterator position, T&& x);
      97           0 :   iterator insert(const_iterator position, size_type n, const T& x);
      98             :   template <
      99             :       typename InputIterator,
     100             :       Requires<static_deque_detail::is_input_iterator<InputIterator>> = nullptr>
     101           0 :   iterator insert(const_iterator position, InputIterator first,
     102             :                   InputIterator last);
     103           0 :   iterator insert(const_iterator position, std::initializer_list<T> init);
     104             : 
     105           0 :   void pop_front();
     106           0 :   void pop_back();
     107             : 
     108           0 :   iterator erase(const_iterator position);
     109           0 :   iterator erase(const_iterator first, const_iterator last);
     110             : 
     111           0 :   void swap(StaticDeque& other);
     112           0 :   void clear();
     113             : 
     114           0 :   void pup(PUP::er& p);
     115             : 
     116             :  private:
     117             :   // Operations on the middle of the deque are supposed to shift as
     118             :   // few elements as possible.
     119           0 :   bool should_operate_on_back(const const_iterator& first,
     120             :                               const const_iterator& last) const {
     121             :     return (this->end() - last) <= (first - this->begin());
     122             :   }
     123             : 
     124             :   // Index into the internal array of T of logical index n
     125           0 :   size_type internal_index(size_type n) const {
     126             :     auto index = start_position_ + n;
     127             :     if (index >= Capacity) {
     128             :       index -= Capacity;
     129             :     }
     130             :     ASSERT(index < static_cast<difference_type>(Capacity),
     131             :            "Calculated out-of-range index: " << index);
     132             :     return index;
     133             :   }
     134             : 
     135           0 :   const T* entry_address(size_type n) const {
     136             :     return reinterpret_cast<const T*>(data_) + internal_index(n);
     137             :   }
     138             : 
     139           0 :   T* entry_address(size_type n) {
     140             :     return reinterpret_cast<T*>(data_) + internal_index(n);
     141             :   }
     142             : 
     143             :   // This array contains the memory used to store the elements.  It is
     144             :   // treated as an array of T, except that some elements may be
     145             :   // missing, i.e., be segments of memory not containing objects.
     146             :   // Elements are created using placement new and removed by calling
     147             :   // their destructors.
     148             :   //
     149             :   // Logically, the array is considered to be a periodic structure.
     150             :   // The existing elements are stored contiguously in the logical
     151             :   // structure, which may correspond to a contiguous portion of the
     152             :   // actual memory or a segment at the end and a segment at the
     153             :   // beginning.  This segment can grow or shrink at either end,
     154             :   // allowing insertions and removals at the ends without needing to
     155             :   // move any other entries.
     156             :   //
     157             :   // The start_position_ variable stores the index of the start of the
     158             :   // portion of the array that is in use, in the range [0, Capacity).
     159             :   // Care must be taken to keep this value in range when the start of
     160             :   // the used memory crosses the end of the storage.  The size_
     161             :   // variable stores the number of existing elements, which must be in
     162             :   // the range [0, Capacity].  (This could not be implemented using a
     163             :   // "one past the end" index instead of the size, because that index
     164             :   // is the same for size=0 and size=Capacity.)
     165           0 :   alignas(T) std::byte data_[sizeof(T) * Capacity];
     166           0 :   size_type start_position_{0};
     167           0 :   size_type size_{0};
     168             : };
     169             : 
     170             : template <typename T, size_t Capacity>
     171             : StaticDeque<T, Capacity>::StaticDeque(const size_type count) {
     172             :   resize(count);
     173             : }
     174             : 
     175             : template <typename T, size_t Capacity>
     176             : StaticDeque<T, Capacity>::StaticDeque(const size_type n, const T& value) {
     177             :   resize(n, value);
     178             : }
     179             : 
     180             : template <typename T, size_t Capacity>
     181             : template <typename InputIterator,
     182             :           Requires<static_deque_detail::is_input_iterator<InputIterator>>>
     183             : StaticDeque<T, Capacity>::StaticDeque(const InputIterator first,
     184             :                                       const InputIterator last) {
     185             :   assign(first, last);
     186             : }
     187             : 
     188             : template <typename T, size_t Capacity>
     189             : StaticDeque<T, Capacity>::StaticDeque(const StaticDeque& other)
     190             :     : StaticDeque(other.begin(), other.end()) {}
     191             : 
     192             : template <typename T, size_t Capacity>
     193             : StaticDeque<T, Capacity>::StaticDeque(StaticDeque&& other)
     194             :     : StaticDeque(std::move_iterator(other.begin()),
     195             :                   std::move_iterator(other.end())) {}
     196             : 
     197             : template <typename T, size_t Capacity>
     198             : StaticDeque<T, Capacity>::StaticDeque(const std::initializer_list<T> init)
     199             :     : StaticDeque(init.begin(), init.end()) {}
     200             : 
     201             : template <typename T, size_t Capacity>
     202             : StaticDeque<T, Capacity>::~StaticDeque() {
     203             :   clear();
     204             : }
     205             : 
     206             : template <typename T, size_t Capacity>
     207             : auto StaticDeque<T, Capacity>::operator=(const StaticDeque& other)
     208             :     -> StaticDeque& {
     209             :   if (size() > other.size()) {
     210             :     resize(other.size());
     211             :   }
     212             :   size_t i = 0;
     213             :   for (; i < size(); ++i) {
     214             :     (*this)[i] = other[i];
     215             :   }
     216             :   for (; i < other.size(); ++i) {
     217             :     push_back(other[i]);
     218             :   }
     219             :   return *this;
     220             : }
     221             : 
     222             : template <typename T, size_t Capacity>
     223             : auto StaticDeque<T, Capacity>::operator=(StaticDeque&& other) -> StaticDeque& {
     224             :   if (size() > other.size()) {
     225             :     resize(other.size());
     226             :   }
     227             :   size_t i = 0;
     228             :   for (; i < size(); ++i) {
     229             :     (*this)[i] = std::move(other[i]);
     230             :   }
     231             :   for (; i < other.size(); ++i) {
     232             :     push_back(std::move(other[i]));
     233             :   }
     234             :   return *this;
     235             : }
     236             : 
     237             : template <typename T, size_t Capacity>
     238             : auto StaticDeque<T, Capacity>::operator=(const std::initializer_list<T> init)
     239             :     -> StaticDeque& {
     240             :   assign(init);
     241             :   return *this;
     242             : }
     243             : 
     244             : template <typename T, size_t Capacity>
     245             : template <typename InputIterator,
     246             :           Requires<static_deque_detail::is_input_iterator<InputIterator>>>
     247             : void StaticDeque<T, Capacity>::assign(InputIterator first,
     248             :                                       const InputIterator last) {
     249             :   // This could be slightly more efficient if this object is not
     250             :   // already empty by assigning to existing objects.
     251             :   clear();
     252             :   while (first != last) {
     253             :     push_back(*first++);
     254             :   }
     255             : }
     256             : 
     257             : template <typename T, size_t Capacity>
     258             : void StaticDeque<T, Capacity>::assign(const size_type count, const T& value) {
     259             :   ASSERT(count <= max_size(), count << " exceeds max size " << max_size());
     260             :   // This could be slightly more efficient if this object is not
     261             :   // already empty by assigning to existing objects.
     262             :   clear();
     263             :   resize(count, value);
     264             : }
     265             : 
     266             : template <typename T, size_t Capacity>
     267             : void StaticDeque<T, Capacity>::assign(const std::initializer_list<T> init) {
     268             :   assign(init.begin(), init.end());
     269             : }
     270             : 
     271             : template <typename T, size_t Capacity>
     272             : void StaticDeque<T, Capacity>::resize(const size_type count) {
     273             :   ASSERT(count <= max_size(), count << " exceeds max size " << max_size());
     274             :   while (size() > count) {
     275             :     pop_back();
     276             :   }
     277             :   while (size() < count) {
     278             :     emplace_back();
     279             :   }
     280             : }
     281             : 
     282             : template <typename T, size_t Capacity>
     283             : void StaticDeque<T, Capacity>::resize(const size_type count, const T& value) {
     284             :   ASSERT(count <= max_size(), count << " exceeds max size " << max_size());
     285             :   while (size() > count) {
     286             :     pop_back();
     287             :   }
     288             :   while (size() < count) {
     289             :     push_back(value);
     290             :   }
     291             : }
     292             : 
     293             : template <typename T, size_t Capacity>
     294             : template <typename... Args>
     295             : T& StaticDeque<T, Capacity>::emplace_front(Args&&... args) {
     296             :   ASSERT(size() < max_size(), "Container is full.");
     297             :   auto* const new_entry =
     298             :       new (entry_address(Capacity - 1)) T(std::forward<Args>(args)...);
     299             :   // This must be done last in case T's constructor throws an
     300             :   // exception.
     301             :   if (start_position_ == 0) {
     302             :     start_position_ = Capacity - 1;
     303             :   } else {
     304             :     --start_position_;
     305             :   }
     306             :   ++size_;
     307             :   return *new_entry;
     308             : }
     309             : 
     310             : template <typename T, size_t Capacity>
     311             : template <typename... Args>
     312             : T& StaticDeque<T, Capacity>::emplace_back(Args&&... args) {
     313             :   ASSERT(size() < max_size(), "Container is full.");
     314             :   auto* const new_entry =
     315             :       new (entry_address(size())) T(std::forward<Args>(args)...);
     316             :   // This must be done last in case T's constructor throws an
     317             :   // exception.
     318             :   ++size_;
     319             :   return *new_entry;
     320             : }
     321             : 
     322             : template <typename T, size_t Capacity>
     323             : template <typename... Args>
     324           0 : auto StaticDeque<T, Capacity>::emplace(const const_iterator position,
     325             :                                        Args&&... args) -> iterator {
     326             :   if (position == this->begin()) {
     327             :     emplace_front(std::forward<Args>(args)...);
     328             :     return this->begin();
     329             :   } else if (position == this->end()) {
     330             :     emplace_back(std::forward<Args>(args)...);
     331             :     return this->end() - 1;
     332             :   }
     333             : 
     334             :   const auto element_to_assign = position - this->begin();
     335             :   if (should_operate_on_back(position, position)) {
     336             :     emplace_back(std::move(this->back()));
     337             :     std::move_backward(this->begin() + element_to_assign, this->end() - 2,
     338             :                        this->end() - 1);
     339             :   } else {
     340             :     emplace_front(std::move(this->front()));
     341             :     std::move(this->begin() + 2, this->begin() + element_to_assign + 1,
     342             :               this->begin() + 1);
     343             :   }
     344             : 
     345             :   const auto new_element = this->begin() + element_to_assign;
     346             :   // emplace in the middle does not construct in place.  STL
     347             :   // containers also have this behavior.
     348             :   *new_element = T(std::forward<Args>(args)...);
     349             :   return new_element;
     350             : }
     351             : 
     352             : template <typename T, size_t Capacity>
     353             : auto StaticDeque<T, Capacity>::insert(const const_iterator position, const T& x)
     354             :     -> iterator {
     355             :   return emplace(position, x);
     356             : }
     357             : 
     358             : template <typename T, size_t Capacity>
     359             : auto StaticDeque<T, Capacity>::insert(const const_iterator position, T&& x)
     360             :     -> iterator {
     361             :   return emplace(position, std::move(x));
     362             : }
     363             : 
     364             : template <typename T, size_t Capacity>
     365             : auto StaticDeque<T, Capacity>::insert(const const_iterator position,
     366             :                                       const size_type n, const T& x)
     367             :     -> iterator {
     368             :   // In theory we could insert these in place and save a bunch of
     369             :   // moves, but keeping track of when elements must be assigned
     370             :   // vs. constructed would be a pain, and this can't be that common an
     371             :   // operation.
     372             :   if (should_operate_on_back(position, position)) {
     373             :     // We are about to invalidate position.
     374             :     const auto insert_index = position - this->begin();
     375             :     const auto old_size = static_cast<difference_type>(size());
     376             :     for (size_type i = 0; i < n; ++i) {
     377             :       emplace_back(x);
     378             :     }
     379             :     const auto new_position = this->begin() + insert_index;
     380             :     const auto old_end = this->begin() + old_size;
     381             :     std::rotate(new_position, old_end, this->end());
     382             :     return new_position;
     383             :   } else {
     384             :     // We are about to invalidate position.
     385             :     const auto insert_index = position - this->begin();
     386             :     const auto insert_back_index = this->end() - position;
     387             :     const auto old_size = static_cast<difference_type>(size());
     388             :     for (size_type i = 0; i < n; ++i) {
     389             :       emplace_front(x);
     390             :     }
     391             :     const auto new_position = this->begin() + insert_index;
     392             :     const auto new_position_end = this->end() - insert_back_index;
     393             :     const auto old_begin = this->end() - old_size;
     394             :     std::rotate(this->begin(), old_begin, new_position_end);
     395             :     return new_position;
     396             :   }
     397             : }
     398             : 
     399             : template <typename T, size_t Capacity>
     400             : template <typename InputIterator,
     401             :           Requires<static_deque_detail::is_input_iterator<InputIterator>>>
     402           0 : auto StaticDeque<T, Capacity>::insert(const const_iterator position,
     403             :                                       InputIterator first,
     404             :                                       const InputIterator last) -> iterator {
     405             :   // The iterators could be single-pass, so we can't clear space and
     406             :   // then insert the new elements.  We also can't do repeated single
     407             :   // insertions because that's too inefficient.  Instead, we have to
     408             :   // create the new elements at the end and then rotate them into
     409             :   // place.
     410             :   if (should_operate_on_back(position, position)) {
     411             :     // We are about to invalidate position.
     412             :     const auto insert_index = position - this->begin();
     413             :     const auto old_size = static_cast<difference_type>(size());
     414             :     while (first != last) {
     415             :       emplace_back(*first++);
     416             :     }
     417             :     const auto new_position = this->begin() + insert_index;
     418             :     const auto old_end = this->begin() + old_size;
     419             :     std::rotate(new_position, old_end, this->end());
     420             :     return new_position;
     421             :   } else {
     422             :     // We are about to invalidate position.
     423             :     const auto insert_index = position - this->begin();
     424             :     const auto insert_back_index = this->end() - position;
     425             :     const auto old_size = static_cast<difference_type>(size());
     426             :     while (first != last) {
     427             :       emplace_front(*first++);
     428             :     }
     429             :     const auto new_position = this->begin() + insert_index;
     430             :     const auto new_position_end = this->end() - insert_back_index;
     431             :     const auto old_begin = this->end() - old_size;
     432             :     std::reverse(this->begin(), old_begin);
     433             :     std::rotate(this->begin(), old_begin, new_position_end);
     434             :     return new_position;
     435             :   }
     436             : }
     437             : 
     438             : template <typename T, size_t Capacity>
     439             : auto StaticDeque<T, Capacity>::insert(const const_iterator position,
     440             :                                       const std::initializer_list<T> init)
     441             :     -> iterator {
     442             :   return insert(position, init.begin(), init.end());
     443             : }
     444             : 
     445             : template <typename T, size_t Capacity>
     446             : void StaticDeque<T, Capacity>::pop_front() {
     447             :   ASSERT(not this->empty(), "Container is empty.");
     448             :   if (start_position_ == Capacity - 1) {
     449             :     start_position_ = 0;
     450             :   } else {
     451             :     ++start_position_;
     452             :   }
     453             :   --size_;
     454             :   // Calling the destructor is sufficient to remove the element.
     455             :   // There is no special "placement delete".
     456             :   entry_address(Capacity - 1)->~T();
     457             : }
     458             : 
     459             : template <typename T, size_t Capacity>
     460             : void StaticDeque<T, Capacity>::pop_back() {
     461             :   ASSERT(not this->empty(), "Container is empty.");
     462             :   --size_;
     463             :   // Calling the destructor is sufficient to remove the element.
     464             :   // There is no special "placement delete".
     465             :   entry_address(size())->~T();
     466             : }
     467             : 
     468             : template <typename T, size_t Capacity>
     469             : auto StaticDeque<T, Capacity>::erase(const const_iterator position)
     470             :     -> iterator {
     471             :   return erase(position, position + 1);
     472             : }
     473             : 
     474             : template <typename T, size_t Capacity>
     475             : auto StaticDeque<T, Capacity>::erase(const const_iterator first,
     476             :                                      const const_iterator last) -> iterator {
     477             :   const auto result_offset = first - this->begin();
     478             :   auto num_to_remove = last - first;
     479             :   if (num_to_remove == 0) {
     480             :     return last - this->begin() + this->begin();
     481             :   }
     482             :   if (should_operate_on_back(first, last)) {
     483             :     std::move(last, this->cend(), first - this->begin() + this->begin());
     484             :     while (num_to_remove-- != 0) {
     485             :       pop_back();
     486             :     }
     487             :   } else {
     488             :     std::move_backward(this->cbegin(), first,
     489             :                        last - this->begin() + this->begin());
     490             :     while (num_to_remove-- != 0) {
     491             :       pop_front();
     492             :     }
     493             :   }
     494             :   return this->begin() + result_offset;
     495             : }
     496             : 
     497             : template <typename T, size_t Capacity>
     498             : void StaticDeque<T, Capacity>::swap(StaticDeque& other) {
     499             :   using std::swap;
     500             :   swap(*this, other);
     501             : }
     502             : 
     503             : template <typename T, size_t Capacity>
     504             : void StaticDeque<T, Capacity>::clear() {
     505             :   while (not this->empty()) {
     506             :     pop_back();
     507             :   }
     508             : }
     509             : 
     510             : template <typename T, size_t Capacity>
     511             : void StaticDeque<T, Capacity>::pup(PUP::er& p) {
     512             :   // If performance when serializing simple types becomes an issue, we
     513             :   // can just send the array of bytes and the other member variables,
     514             :   // which will probably perform better (unless, perhaps, the
     515             :   // container is nearly empty).  That can't be done for types that
     516             :   // are not trivially copyable, which would still need to use
     517             :   // something like the current implementation.
     518             :   size_type sz = size();
     519             :   p | sz;
     520             :   resize(sz);
     521             :   for (auto& entry : *this) {
     522             :     p | entry;
     523             :   }
     524             : }
     525             : 
     526             : template <typename T, size_t Capacity>
     527           0 : void swap(StaticDeque<T, Capacity>& x, StaticDeque<T, Capacity>& y) {
     528             :   const auto initial_size = x.size();
     529             :   if (initial_size > y.size()) {
     530             :     return swap(y, x);
     531             :   }
     532             :   for (size_t i = 0; i < initial_size; ++i) {
     533             :     using std::swap;
     534             :     swap(x[i], y[i]);
     535             :   }
     536             :   const auto to_move =
     537             :       y.begin() +
     538             :       static_cast<typename StaticDeque<T, Capacity>::difference_type>(
     539             :           initial_size);
     540             :   x.insert(x.end(), std::move_iterator(to_move), std::move_iterator(y.end()));
     541             :   y.erase(to_move, y.end());
     542             : }
     543             : 
     544             : template <typename T, size_t Capacity>
     545           0 : std::ostream& operator<<(std::ostream& s,
     546             :                          const StaticDeque<T, Capacity>& deque) {
     547             :   sequence_print_helper(s, deque.begin(), deque.end());
     548             :   return s;
     549             : }

Generated by: LCOV version 1.14