#pragma once #include #include #include namespace gp{ /** * @brief A vector type most similar to that of the standard library * Always uses polymorphic allocation, no small value optimization * * @tparam T */ template class vector final { T* ary = nullptr; //< The data size_t sz = 0; //< the used size of the array size_t cap = 0; //< the available capacity of the array gp::reference_wrapper alloc; //< the allocator public: using associated_iterator = pointer_iterator; using associated_const_iterator = const_pointer_iterator; using associated_riterator = pointer_iterator; using associated_const_riterator = const_pointer_iterator; /** * @brief Construct a new vector object from an allocator * * @param v the allocator */ vector(allocator& v) : ary() , alloc(v) {} /** * @brief Construct a new vector object from another vector, copying it * * @param oth the other vector */ vector(vector& oth) : alloc(oth.alloc) { sz = 0; cap = 0; ary = nullptr; gp_config::assertion(reserve(oth.size()), "could not reserve space on building"); sz = oth.size(); cap = oth.size(); auto it_l = begin(); auto it_o = oth.cbegin(); for(size_t i = 0; i < sz; ++i) { new(&*(it_l++)) T(*(it_o++)); } } /** * @brief Construct a new vector object by moving objects out of another vector * * @param oth */ vector(vector&& oth) : ary(oth.ary) , sz(oth.sz) , cap(oth.cap) , alloc(oth.alloc) { oth.sz = 0; oth.cap = 0; oth.ary = nullptr; } /** * @brief Copy assignment * * @param oth * @return vector& the reference to this changed vector */ vector& operator=(vector& oth) { gp_config::assertion(reserve(oth.size()), "could not reserve space on assign"); for(size_t i = 0; i < gp::min(sz, oth.sz); ++i) { if constexpr (!std::is_trivially_destructible_v) ary[i]->~T(); new(ary+i) T(oth[i]); } if(sz < oth.sz) { for(size_t i = sz; i < oth.sz; ++i) { if constexpr (!std::is_trivially_destructible_v) ary[i]->~T(); new(ary+i) T(oth[i]); } } else if(sz > oth.sz) { if constexpr (!std::is_trivially_destructible_v) for(size_t i = oth.sz; i < sz; ++i) { ary[i]->~T(); } } sz = oth.sz; return *this; } /** * @brief Move assignment * * Will not change the allocator of the local vector, is EXPENSIVE if the both vectors have different allocators * * @param oth * @return vector& */ vector& operator=(vector&& oth) { if(&alloc.get() == &oth.alloc.get()) { gp::swap(ary, oth.ary); gp::swap(sz, oth.sz); gp::swap(cap, oth.cap); } else { for(auto& elem : *this) { elem->~T(); } sz = 0; if(capacity()) for(auto& elem : *this) { elem.~T(); } gp_config::assertion(alloc.get().deallocate(ary), "could not deallocate"); ary = nullptr; } } /** * @brief Ensures the vector can hold at least 1 more element * * @return true if it is at least now possible to fit one more element * @return false if fiting one more element is not possible even now */ bool grow() { if(sz == cap) return reserve(1 + sz + (sz >> 1)); return true; } /** * @brief Reserves space so that the capacity is at least equal to the provided value. * * This will never shrink the datastructure * * @param new_cap the new capacity * @return true on success * @return false on failure */ bool reserve(size_t new_cap) { if(new_cap <= cap) return true; size_t new_data_size = new_cap*sizeof(T); if(alloc.get().try_reallocate(ary, new_data_size)) return true; if(T* new_ary = (T*)alloc.get().allocate(new_data_size); new_ary) { auto new_it = new_ary; for(auto& elem : *this) { new(new_it++) T(gp::move(elem)); } if(ary != nullptr) gp_config::assertion(alloc.get().deallocate(ary), "failed to deallocate old range"); ary = new_ary; cap = new_cap; return true; } return false; } constexpr const T& operator[] (size_t off) const { if constexpr (gp_config::has_buffer_bounds) { gp_config::assertion( off < sz, "Array bounds infringed" ); } return ary[off]; } constexpr size_t size() const { return sz; } constexpr size_t capacity() const { return cap; } /** * @brief Adds the provided value to the vector * * @param value * @return true on success * @return false on failure */ constexpr bool push_back(T& value) { if(grow()) { new(ary+sz) T(value); sz++; return true; } return false; } /** * @brief Moves the provided value to the vector * * @param value * @return true on success * @return false on failure */ constexpr bool push_back(T&& value) { if(grow()) { new(ary+sz) T(gp::move(value)); sz++; return true; } return false; } /** * @brief Constructs a new element at the end of the vector * * @param value the parameters to be sent to the constructor of T * @return true on success * @return false on failure */ template constexpr bool emplace_back(U&&... value) { if(grow()) { new(ary+sz) T(gp::forward(value)...); sz++; return true; } return false; } /** * @brief moves the last element of the vector out if it exists, returning an optional. * * @return constexpr gp::optional contains a value if it existed, else it is empty */ constexpr gp::optional pop_back() { if(sz == 0) return gp::nullopt; sz--; gp::optional ret_val = gp::move(ary[sz]); ary[sz]->~T(); return gp::move(ret_val); } void remove(gp::pointer_iterator it) { for(auto step = it + 1; step!=end(); step++) { (*it++) = gp::move(*step); } (*rbegin()).~T(); sz -= 1; } constexpr gp::pointer_iterator begin() { return associated_iterator(&ary[0]); } constexpr gp::pointer_iterator end() { return associated_iterator(&ary[sz]); } constexpr gp::const_pointer_iterator cbegin() const { return associated_const_iterator(&ary[0]); } constexpr gp::const_pointer_iterator cend() const { return associated_const_iterator(&ary[sz]); } constexpr gp::pointer_iterator rbegin() { return associated_riterator(&ary[sz-1]); } constexpr gp::pointer_iterator rend() { return associated_riterator(ary-1); } constexpr gp::const_pointer_iterator crbegin() const { return associated_const_riterator(&ary[sz-1]); } constexpr gp::const_pointer_iterator crend() const { return associated_const_riterator(ary-1); } constexpr bool operator==(const vector& oth) const { for(size_t idx = 0; idx as_buffer() { return gp::buffer{(T*)ary, (T*)ary+sz}; } }; }