/** @file DArenaVector.hpp * * @author Roland Conybeare, Jan 2026 **/ #pragma once #include "DArena.hpp" #include #include // for ::memset() namespace xo { namespace mm { /** @brief vector of T using dedicated DArena for storage * * Replicate (to the extent feasible) std::vector * behavior, but using a dedicated DArena to provide storage * * Unlike std::vector: * 1. does not support copying * 2. capacity fixed at construction time * * @tparam T element type. Must be Erasable **/ template struct DArenaVector { public: using value_type = T; using size_type = std::size_t; using difference_type = std::ptrdiff_t; using reference = value_type &; using const_reference = const value_type &; using iterator = value_type *; using const_iterator = const value_type *; /** null ctor **/ DArenaVector() = default; /** ctor from already-mapped (but not committed) address range_type * vector has size zero **/ DArenaVector(const ArenaConfig & cfg, size_type page_z, size_type arena_align_z, DArena::value_type lo, DArena::value_type hi); /** not intended to be copyable **/ DArenaVector(const DArenaVector &) = delete; /** move ctor **/ DArenaVector(DArenaVector && other); /** releases mapped memory **/ ~DArenaVector(); /** create empty vector using @p cfg to configure backing store **/ static DArenaVector map(const ArenaConfig & cfg); /** true iff vector is emtpy **/ bool empty() const { return size_ == 0; } size_type size() const { return size_; } size_type max_size() const { return capacity(); } size_type capacity() const { return store_.reserved() / sizeof(T); } /** get reference to element at zero-based index @p i. Do not check bounds **/ T & operator[](size_t i) noexcept { return *(this->_address_of(i)); } const T & operator[](size_t i) const noexcept { return *(this->_address_of(i)); } /** get reference to element at zero-based index @p i. Do check bounds **/ T & at(size_type i) { _check_valid_index(i); return *(this->_address_of(i)); } const T & at(size_type i) const { _check_valid_index(i); return *(this->_address_of(i)); } /** get to at first element of vector. Same as @p end if vector is empty **/ iterator begin() noexcept { return this->_address_of(0); } /** get iterator to end of vector - "one past the last element" **/ iterator end() noexcept { return this->_address_of(size_); } const_iterator cbegin() const noexcept { return this->_address_of(0); } const_iterator begin() const noexcept { return this->cbegin(); } const_iterator cend() const noexcept { return this->_address_of(size_); } const_iterator end() const noexcept { return this->cend(); } constexpr const DArena * store() const { return &store_; } constexpr T * data() { return reinterpret_cast(store_.lo_); } constexpr const T * data() const { return reinterpret_cast(store_.lo_); } /** arena used for element storage * (Might prefer obj here; refrain to avoid leveling violation) **/ void visit_pools(const MemorySizeVisitor & fn) const { store_.visit_pools(fn); } /** reserve space, if possible, for at least @p z elements. * Always limited by ArenaConfig.size_ **/ void reserve(size_type z); /** resize to size @p z. Return true on success. May fail iff oom. **/ bool resize(size_type z); void shrink_to_fit(); /** reset vector to empty state **/ void clear(); T & insert(size_type pos, T && x); T & insert(size_type pos, const T & x); void erase(size_type pos); void push_back(T && x); void push_back(const T & x); void swap(DArenaVector & other) noexcept; DArenaVector & operator=(DArenaVector && x) noexcept; private: T * _address_of(size_type i) { return ((T *)store_.lo_) + i; } const T * _address_of(size_type i) const { return ((const T *)store_.lo_) + i; } void _check_valid_index(size_type i) const; private: size_type size_ = 0; DArena store_; DArena::Checkpoint zero_ckp_; }; template DArenaVector::DArenaVector(const ArenaConfig & cfg, size_type page_z, size_type arena_align_z, DArena::value_type lo, DArena::value_type hi) : store_{cfg, page_z, arena_align_z, lo, hi}, zero_ckp_{store_.checkpoint()} {} template DArenaVector::DArenaVector(DArenaVector && other) : size_{other.size_}, store_{std::move(other.store_)}, zero_ckp_{std::move(other.zero_ckp_)} { other.size_ = 0; other.zero_ckp_ = DArena::Checkpoint(); } template DArenaVector::~DArenaVector() { if constexpr (std::is_trivially_destructible_v) { // nothing to do } else { // invoke destructor for each element for (size_type i = 0, n = size(); i < n; ++i) { T & x = (*this)[i]; x.~T(); } } } template DArenaVector & DArenaVector::operator=(DArenaVector && other) noexcept { this->size_ = other.size_; this->store_ = std::move(other.store_); this->zero_ckp_ = std::move(other.zero_ckp_); other.size_ = 0; other.zero_ckp_ = DArena::Checkpoint(); return *this; } template DArenaVector DArenaVector::map(const ArenaConfig & cfg) { DArenaVector retval; retval.store_ = std::move(DArena::map(cfg)); retval.zero_ckp_ = retval.store_.checkpoint(); return retval; } template void DArenaVector::reserve(size_type z) { store_.expand(z * sizeof(T), __PRETTY_FUNCTION__); } template bool DArenaVector::resize(size_type z) { // new arena size in bytes size_t req_z = z * sizeof(T); if (z > size_) { // expand arena to accomodate if (!store_.expand(req_z, __PRETTY_FUNCTION__)) return false; // run ctors if constexpr (std::is_trivially_constructible_v) { ::memset(this->_address_of(size_), 0, req_z - (size_ * sizeof(T))); } else { for (size_type i = size_; i < z; ++i) { void * addr = &(*this)[i]; new (addr) T(); } } } else { if constexpr (std::is_trivially_destructible_v) { // nothing to do } else { // invoke destructor for each discarded element for (size_type i = z; i < size_; ++i) { T & x = (*this)[i]; x.~T(); } } } // rewind to checkpoint, then reallocate. // This is for form's sake, so that DArena considers memory // to be 'allocated'. DArenaVector doesn't care for itself, // but this preserves expected behavior of visit_pools(). // store_.restore(zero_ckp_); store_.alloc(xo::reflect::typeseq::id(), req_z); this->size_ = z; return true; } template void DArenaVector::shrink_to_fit() { // could in principle release unused mapped pages here } template void DArenaVector::clear() { this->resize(0); } template void DArenaVector::_check_valid_index(size_type i) const { if (size_ <= i) throw std::out_of_range("DArenaVector index out of bounds"); } template T & DArenaVector::insert(size_type pos, T && x) { { size_type new_z = size_ + 1; size_type req_z = new_z * sizeof(T); store_.expand(req_z, __PRETTY_FUNCTION__); } // move elements [i .. z-1] right by one position. // must proceed in reverse order! for (size_type ip1 = size_; ip1 > pos; --ip1) { (*this)[ip1] = std::move((*this)[ip1-1]); } T * addr = this->_address_of(pos); new (addr) T{std::move(x)}; this->size_ = size_ + 1; return *addr; } template T & DArenaVector::insert(size_type pos, const T & x) { { size_type new_z = size_ + 1; size_type req_z = new_z * sizeof(T); store_.expand(req_z, __PRETTY_FUNCTION__); } // move elements [i .. z-1] right by one position. // must proceed in reverse order! for (size_type ip1 = size_; ip1 > pos; --ip1) { (*this)[ip1] = std::move((*this)[ip1-1]); } T * addr = this->_address_of(pos); new (addr) T{x}; this->size_ = size_ + 1; return *addr; } template void DArenaVector::erase(size_type pos) { // move elements [pos+1 .. z-1] left by one position. if (pos >= size_) [[unlikely]] return; for (size_type i = pos; i+1 < size_; ++i) { (*this)[i] = std::move((*this)[i+1]); } --(this->size_); } template void DArenaVector::push_back(T && x) { size_type z = size_ + 1; size_type req_z = z * sizeof(T); if (this->store_.expand(req_z, __PRETTY_FUNCTION__)) { T * addr = this->_address_of(size_); new (addr) T{std::move(x)}; this->size_ = z; } } template void DArenaVector::push_back(const T & x) { size_type z = size_ + 1; if (this->store_.expand(z * sizeof(T), __PRETTY_FUNCTION__)) { T * addr = this->_address_of(size_); new (addr) T{x}; this->size_ = z; } } template void DArenaVector::swap(DArenaVector & other) noexcept { std::swap(size_, other.size_); std::swap(store_, other.store_); } } /*namespace mm*/ } /*namespace xo*/ /* end DArenaVector.hpp */