LCOV - code coverage report
Current view: top level - source/ps/containers - StaticVector.h (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 109 118 92.4 %
Date: 2023-01-19 00:18:29 Functions: 142 276 51.4 %

          Line data    Source code
       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           2 : struct CapacityExceededException : public std::length_error
      35             : {
      36           2 :     using std::length_error::length_error;
      37             : };
      38             : 
      39             : template<size_t N>
      40             : constexpr auto MakeSmallestCapableUnsigned()
      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>
      88             : class StaticVector
      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>());
      96             :     using reference = value_type&;
      97             :     using const_reference = const value_type&;
      98             :     using pointer = value_type*;
      99             :     using const_pointer = const value_type*;
     100             :     using iterator = pointer;
     101             :     using const_iterator = const_pointer;
     102             :     using reverse_iterator = std::reverse_iterator<iterator>;
     103             :     using const_reverse_iterator = std::reverse_iterator<const_iterator>;
     104             : 
     105             : 
     106           1 :     StaticVector() = default;
     107           7 :     StaticVector(const StaticVector& other) noexcept(std::is_nothrow_copy_constructible_v<T>)
     108           7 :         : m_Size{other.size()}
     109             :     {
     110           7 :         std::uninitialized_copy(other.begin(), other.end(), begin());
     111           7 :     }
     112             : 
     113             :     template<size_t OtherN>
     114           1 :     explicit StaticVector(const StaticVector<T, OtherN>& other) noexcept(
     115             :         std::is_nothrow_copy_constructible_v<T>)
     116           1 :         : m_Size{other.size()}
     117             :     {
     118             :         static_assert(OtherN < N);
     119             : 
     120           1 :         std::uninitialized_copy(other.begin(), other.end(), begin());
     121           1 :     }
     122             : 
     123           3 :     StaticVector& operator=(const StaticVector& other) noexcept(std::is_nothrow_copy_constructible_v<T>
     124             :         && std::is_nothrow_copy_assignable_v<T>)
     125             :     {
     126           3 :         const size_type initializedCopies{std::min(other.size(), size())};
     127           3 :         std::copy_n(other.begin(), initializedCopies, begin());
     128           3 :         std::uninitialized_copy(other.begin() + initializedCopies, other.end(),
     129           3 :             begin() + initializedCopies);
     130           3 :         std::destroy(begin() + initializedCopies, end());
     131             : 
     132           3 :         m_Size = other.size();
     133           3 :         return *this;
     134             :     }
     135             : 
     136             :     template<size_t OtherN>
     137             :     StaticVector& operator=(const StaticVector<T, OtherN>& other) noexcept(
     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>
     159             :     explicit StaticVector(StaticVector<T, OtherN>&& other)
     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>
     182             :     StaticVector& operator=(StaticVector<T, OtherN>&& other) noexcept(
     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             : 
     197          28 :     ~StaticVector()
     198             :     {
     199          28 :         clear();
     200          28 :     }
     201             : 
     202           2 :     StaticVector(const size_type count, const T& value)
     203           2 :         : m_Size{count}
     204             :     {
     205           2 :         if (count > N)
     206             :             throw CapacityExceededException{fmt::format(
     207             :                 "Tried to construct a StaticVector with a size of {} but the capacity is only {}",
     208           0 :                 count, N)};
     209             : 
     210           2 :         std::uninitialized_fill(begin(), end(), value);
     211           2 :     }
     212             : 
     213           3 :     StaticVector(const size_type count)
     214           3 :         : m_Size{count}
     215             :     {
     216           3 :         if (count > N)
     217             :             throw CapacityExceededException{fmt::format(
     218             :                 "Tried to construct a StaticVector with a size of {} but the capacity is only {}",
     219           1 :                 count, N)};
     220             : 
     221           2 :         std::uninitialized_default_construct(begin(), end());
     222           2 :     }
     223             : 
     224          15 :     StaticVector(const std::initializer_list<T> init)
     225          15 :         : m_Size{static_cast<size_type>(init.size())} // Will be tested below.
     226             :     {
     227          15 :         if (init.size() > N)
     228           0 :             throw CapacityExceededException{fmt::format(
     229             :                 "Tried to construct a StaticVector with a size of {} but the capacity is only {}",
     230           0 :                 init.size(), N)};
     231             : 
     232          15 :         std::uninitialized_copy(init.begin(), init.end(), begin());
     233          15 :     }
     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           1 :     reference at(const size_type index)
     250             :     {
     251           1 :         if (index >= m_Size)
     252             :             throw std::out_of_range{fmt::format("Called at({}) but there are only {} elements.",
     253           1 :                 index, size())};
     254             : 
     255           0 :         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           8 :     reference operator[](const size_type index) noexcept
     268             :     {
     269           8 :         ASSERT(index < size());
     270           8 :         return *(begin() + index);
     271             :     }
     272             : 
     273           0 :     const_reference operator[](const size_type index) const noexcept
     274             :     {
     275           0 :         ASSERT(index < size());
     276           0 :         return *(begin() + index);
     277             :     }
     278             : 
     279           1 :     reference front() noexcept
     280             :     {
     281           1 :         ASSERT(!empty());
     282           1 :         return *begin();
     283             :     }
     284             : 
     285             :     const_reference front() const noexcept
     286             :     {
     287             :         ASSERT(!empty());
     288             :         return *begin();
     289             :     }
     290             : 
     291           5 :     reference back() noexcept
     292             :     {
     293           5 :         ASSERT(!empty());
     294           5 :         return *std::prev(end());
     295             :     }
     296             : 
     297             :     const_reference back() const noexcept
     298             :     {
     299             :         ASSERT(!empty());
     300             :         return *std::prev(end());
     301             :     }
     302             : 
     303         145 :     pointer data() noexcept
     304             :     {
     305         145 :         return std::launder(reinterpret_cast<pointer>(m_Data.data()));
     306             :     }
     307             : 
     308          57 :     const_pointer data() const noexcept
     309             :     {
     310          57 :         return std::launder(reinterpret_cast<const_pointer>(m_Data.data()));
     311             :     }
     312             : 
     313             : 
     314         145 :     iterator begin() noexcept
     315             :     {
     316         145 :         return data();
     317             :     }
     318             : 
     319          30 :     const_iterator begin() const noexcept
     320             :     {
     321          30 :         return cbegin();
     322             :     }
     323             : 
     324          57 :     const_iterator cbegin() const noexcept
     325             :     {
     326          57 :         return data();
     327             :     }
     328             : 
     329          53 :     iterator end() noexcept
     330             :     {
     331          53 :         return begin() + size();
     332             :     }
     333             : 
     334          27 :     const_iterator end() const noexcept
     335             :     {
     336          27 :         return cend();
     337             :     }
     338             : 
     339          27 :     const_iterator cend() const noexcept
     340             :     {
     341          27 :         return cbegin() + size();
     342             :     }
     343             : 
     344             :     reverse_iterator rbegin() noexcept
     345             :     {
     346             :         return std::make_reverse_iterator(end());
     347             :     }
     348             : 
     349             :     const_reverse_iterator rbegin() const noexcept
     350             :     {
     351             :         return crbegin();
     352             :     }
     353             : 
     354             :     const_reverse_iterator crbegin() const noexcept
     355             :     {
     356             :         return std::make_reverse_iterator(end());
     357             :     }
     358             : 
     359             :     reverse_iterator rend() noexcept
     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          11 :     bool empty() const noexcept
     375             :     {
     376          11 :         return size() == 0;
     377             :     }
     378             : 
     379           7 :     bool full() const noexcept
     380             :     {
     381           7 :         return size() == N;
     382             :     }
     383             : 
     384         131 :     size_type size() const noexcept
     385             :     {
     386         131 :         return m_Size;
     387             :     }
     388             : 
     389             :     constexpr size_type capacity() const noexcept
     390             :     {
     391             :         return N;
     392             :     }
     393             : 
     394             : 
     395          30 :     void clear() noexcept
     396             :     {
     397          30 :         std::destroy(begin(), end());
     398          30 :         m_Size = 0;
     399          30 :     }
     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           1 :     iterator insert(const const_iterator location, T&& value)
     437             :     {
     438           1 :         if (full())
     439           0 :             throw CapacityExceededException{"Called insert but the StaticVector is already full"};
     440             : 
     441           1 :         if (location == end())
     442           0 :             return std::addressof(emplace_back(std::move(value)));
     443             : 
     444           1 :         const iterator mutableLocation{MakeMutableIterator(location)};
     445           1 :         new(end()) T{std::move(back())};
     446           1 :         ++m_Size;
     447             : 
     448           1 :         std::move_backward(mutableLocation, end() - 2, end() -1);
     449             : 
     450           1 :         *mutableLocation = std::move(value);
     451           1 :         return mutableLocation;
     452             :     }
     453             : 
     454             :     /**
     455             :      * If an exception is thrown this function has no effect
     456             :      * (strong exception guarantee).
     457             :      */
     458           2 :     void push_back(const T& value)
     459             :     {
     460           2 :         emplace_back(value);
     461           2 :     }
     462             : 
     463             :     /**
     464             :      * If an exception is thrown this function has no effect
     465             :      * (strong exception guarantee).
     466             :      */
     467           1 :     void push_back(T&& value)
     468             :     {
     469           1 :         emplace_back(std::move(value));
     470           1 :     }
     471             : 
     472             :     /**
     473             :      * If an exception is thrown this function has no effect
     474             :      * (strong exception guarantee).
     475             :      */
     476             :     template<typename... Args>
     477           6 :     reference emplace_back(Args&&... args)
     478             :     {
     479           6 :         if (full())
     480             :             throw CapacityExceededException{
     481           1 :                 "Called emplace_back but the StaticVector is already full"};
     482             : 
     483           5 :         const iterator location{begin() + size()};
     484           5 :         new(location) T{std::forward<Args>(args)...};
     485           5 :         ++m_Size;
     486           5 :         return *location;
     487             :     }
     488             : 
     489           4 :     void pop_back() noexcept
     490             :     {
     491           4 :         ASSERT(!empty());
     492           4 :         std::destroy_at(std::addressof(back()));
     493           4 :         --m_Size;
     494           4 :     }
     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           8 :     friend bool operator==(const StaticVector<T, N>& lhs, const StaticVector<T, OtherN>& rhs)
     545             :     {
     546           8 :         return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
     547             :     }
     548             : 
     549             :     template<size_t OtherN>
     550           5 :     friend bool operator!=(const StaticVector<T, N>& lhs, const StaticVector<T, OtherN>& rhs)
     551             :     {
     552           5 :         return !(lhs == rhs);
     553             :     }
     554             : 
     555             : private:
     556           1 :     iterator MakeMutableIterator(const const_iterator iter) noexcept
     557             :     {
     558           1 :         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

Generated by: LCOV version 1.13