General Purpose library for Freestanding C++ and POSIX systems
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

372 lines
7.9 KiB

#pragma once
#include <gp/containers/buffer.hpp>
#include <gp/utils/allocators/allocator.hpp>
#include <initializer_list>
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<typename T>
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<allocator> alloc; //< the allocator
public:
using associated_iterator = pointer_iterator<T, 1>;
using associated_const_iterator = const_pointer_iterator<T, 1>;
using associated_riterator = pointer_iterator<T, -1>;
using associated_const_riterator = const_pointer_iterator<T, -1>;
/**
* @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<T>) 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<T>) ary[i]->~T();
new(ary+i) T(oth[i]);
}
} else if(sz > oth.sz) {
if constexpr (!std::is_trivially_destructible_v<T>)
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()<oth.size()) {
reserve(oth.size());
}
size_t idx = 0;
for(auto& elem : oth) {
new(ary+idx) T(gp::move(elem));
++idx;
elem.~T();
}
gp_config::assertion(alloc.get().deallocate(oth.ary), "could not deallocate");
sz = idx;
oth.sz = 0;
oth.capacity = 0;
oth.ary = nullptr;
}
return *this;
}
constexpr T& operator[] (size_t off)
{
if constexpr (gp_config::has_buffer_bounds)
{
gp_config::assertion(
off < sz,
"Array bounds infringed"
);
}
return ary[off];
}
~vector()
{
if(ary)
{
if constexpr (!std::is_trivially_destructible_v<T>)
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<typename ...U>
constexpr bool emplace_back(U&&... value) {
if(grow()) {
new(ary+sz) T(gp::forward<U>(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<T> contains a value if it existed, else it is empty
*/
constexpr gp::optional<T> pop_back() {
if(sz == 0) return gp::nullopt;
sz--;
gp::optional<T> ret_val = gp::move(ary[sz]);
ary[sz]->~T();
return gp::move(ret_val);
}
void remove(gp::pointer_iterator<T, 1> it) {
for(auto step = it + 1; step!=end(); step++) {
(*it++) = gp::move(*step);
}
(*rbegin()).~T();
sz -= 1;
}
constexpr gp::pointer_iterator<T, 1> begin()
{
return associated_iterator(&ary[0]);
}
constexpr gp::pointer_iterator<T, 1> end()
{
return associated_iterator(&ary[sz]);
}
constexpr gp::const_pointer_iterator<T, 1> cbegin() const
{
return associated_const_iterator(&ary[0]);
}
constexpr gp::const_pointer_iterator<T, 1> cend() const
{
return associated_const_iterator(&ary[sz]);
}
constexpr gp::pointer_iterator<T, -1> rbegin()
{
return associated_riterator(&ary[sz-1]);
}
constexpr gp::pointer_iterator<T, -1> rend()
{
return associated_riterator(ary-1);
}
constexpr gp::const_pointer_iterator<T, -1> crbegin() const
{
return associated_const_riterator(&ary[sz-1]);
}
constexpr gp::const_pointer_iterator<T, -1> crend() const
{
return associated_const_riterator(ary-1);
}
constexpr bool operator==(const vector& oth) const
{
for(size_t idx = 0; idx<sz; idx++)
{
if(ary[idx] != oth.ary[idx])
{
return false;
}
}
return true;
}
constexpr bool operator!=(const vector& oth) const
{
return !(*this == oth);
}
/**
* @brief Provides a span access to the vector
*
* @return A buffer of the vector.
* It is invalidated by any operation that may change the size of the vector.
*/
gp::buffer<T> as_buffer()
{
return gp::buffer<T>{(T*)ary, (T*)ary+sz};
}
};
}