Files
fennec/include/fennec/containers/dynarray.h
2026-01-06 19:48:28 -05:00

628 lines
17 KiB
C++

// =====================================================================================================================
// fennec, a free and open source game engine
// Copyright © 2025 - 2026 Medusa Slockbower
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
// =====================================================================================================================
///
/// \file fennec/containers/dynarray.h
/// \brief A header containing the definition for a dynamically allocated array
///
///
/// \details
/// \author Medusa Slockbower
///
/// \copyright Copyright © 2025 - 2026 Medusa Slockbower ([GPLv3](https://www.gnu.org/licenses/gpl-3.0.en.html))
///
///
#ifndef FENNEC_CONTAINERS_DYNARRAY_H
#define FENNEC_CONTAINERS_DYNARRAY_H
#include <fennec/containers/initializer_list.h>
#include <fennec/lang/utility.h>
#include <fennec/memory/allocator.h>
#include <fennec/memory/new.h>
namespace fennec
{
///
///
/// \brief Wrapper for dynamically sized and allocated arrays
/// \details
/// | Property | Value |
/// |:-----------:|:----------:|
/// | stable | ⛔ |
/// | dynamic | ✅ |
/// | homogeneous | ✅ |
/// | distinct | ⛔ |
/// | ordered | ⛔ |
/// | space | \f$O(N)\f$ |
/// | linear | ✅ |
/// | access | \f$O(1)\f$ |
/// | find | \f$O(N)\f$ |
/// | insertion | \f$O(N)\f$ |
/// | deletion | \f$O(N)\f$ |
///
/// This structure prefers shallow moves and deep copies.
///
/// \tparam TypeT value type
template<class TypeT, class Alloc = allocator<TypeT>>
struct dynarray {
// Definitions =========================================================================================================
public:
using value_t = TypeT; ///< Alias for the value type
using alloc_t = Alloc; ///< Alias for the allocator type
// Constructors ========================================================================================================
public:
/// \name Constructors & Destructor
/// @{
///
/// \brief Default Constructor, initializes an empty allocation.
constexpr dynarray()
: _alloc(8)
, _size(0) {
}
///
/// \brief Alloc Constructor, initialize empty allocation with allocator instance.
/// \param alloc An allocator object to copy, for instances where the allocator needs to be initialized with some
/// data.
explicit constexpr dynarray(const alloc_t& alloc)
: _alloc(8, alloc)
, _size(0) {
}
///
/// \brief Alloc Move Constructor, initialize empty allocation with allocator instance.
/// \param alloc An allocator object to copy, for instances where the allocator needs to be initialized with some
/// data.
explicit constexpr dynarray(alloc_t&& alloc) noexcept
: _alloc(8, alloc)
, _size(0) {
}
///
/// \brief Sized Allocation, initializes a dynarray with \f$n\f$ elements using the default constructor.
/// \param n The number of elements.
explicit constexpr dynarray(size_t n)
: _alloc(n)
, _size(n)
{
value_t* addr = _alloc.data();
for(; n > 0; --n, ++addr) {
fennec::construct(addr);
}
}
///
/// \brief Sized Allocation Alloc Constructor, initializes a dynarray with allocator \f$alloc\f$ and \f$n\f$ elements
/// using the default constructor.
/// \param n The number of elements
/// \param alloc The allocator object to copy
constexpr dynarray(size_t n, const alloc_t& alloc)
: _alloc(n, alloc)
, _size(n) {
value_t* addr = _alloc.data();
for(; n > 0; --n, ++addr) {
fennec::construct(addr);
}
}
///
/// \brief Sized Allocation Alloc Move Constructor, initializes a dynarray with allocator \f$alloc\f$ and \f$n\f$ elements
/// using the default constructor.
/// \param n The number of elements
/// \param alloc The allocator object to copy
constexpr dynarray(size_t n, alloc_t&& alloc)
: _alloc(n, alloc)
, _size(n) {
value_t* addr = _alloc.data();
for(; n > 0; --n, ++addr) {
fennec::construct(addr);
}
}
///
/// \brief Sized Allocation Copy Constructor, Create an allocation of size \f$n\f$ elements, with each element
/// constructed using the copy constructor
/// \param n the number of elements
/// \param val the value to copy
constexpr dynarray(size_t n, const TypeT& val)
: _alloc(n)
, _size(n) {
value_t* addr = _alloc.data();
for(; n > 0; --n, ++addr) {
fennec::construct(addr, val);
}
}
// This constructor should not be invokable since moving is a single object operation and will cause undefined
// behaviour when moving to multiple elements
constexpr dynarray(size_t n, TypeT&& val) = delete;
///
/// \brief Emplace Constructor
/// \tparam ArgsT A sequence of argument types
/// \param n The number of objects to create
/// \param args The arguments to create each object with
template<typename...ArgsT>
constexpr explicit dynarray(size_t n, ArgsT&&...args)
: _alloc(n)
, _size(n) {
for(; n > 0; --n) {
fennec::construct(&_alloc[n], fennec::forward<ArgsT>(args)...);
}
}
///
/// \brief Array Copy Constructor
/// \tparam N The length of the array, automatically deduced
/// \param arr The array to copy
template<size_t N>
constexpr dynarray(const TypeT (&arr)[N])
: _alloc(N)
, _size(N) {
for (size_t i = 0; i < N; ++i) {
_alloc[i] = arr[i];
}
}
///
/// \brief Array Move Constructor
/// \tparam N The length of the array, automatically deduced
/// \param arr The array to move
template<size_t N>
constexpr dynarray(TypeT (&&arr)[N])
: _alloc(N)
, _size(N) {
for (size_t i = 0; i < N; ++i) {
_alloc[i] = fennec::move(arr[i]);
}
}
///
/// \brief Conversion Constructor, copies elements of conv as this \f$value_t\f$
/// \tparam OTypeT The other value type
/// \tparam OAlloc The other allocator type
/// \param conv The dynarray to convert
template<typename OTypeT, class OAlloc>
constexpr dynarray(const dynarray<OTypeT, OAlloc>& conv)
: _alloc(conv.size())
, _size(conv.size()) {
size_t i = 0;
for (const auto& it : conv) {
fennec::construct(&_alloc[i++], it);
}
}
///
/// \brief Initializer List Constructor
/// \param l List of elements to initialize with
/// \param alloc An allocator object to copy
constexpr dynarray(initializer_list<value_t> l, const alloc_t& alloc = alloc_t())
: _alloc(l.size(), alloc)
, _size(l.size()) {
size_t i = 0;
for (auto& it : l) {
fennec::construct(&_alloc[i++], fennec::move(it));
}
}
///
/// \brief Copy Constructor, uses the copy constructor to copy each element
/// \param arr the dynarray to copy
constexpr dynarray(const dynarray& arr)
: _alloc(arr._size)
, _size(arr._size) {
for (size_t i = 0; i < _size; ++i) {
fennec::construct(&_alloc[i], arr[i]);
}
}
///
/// \brief Move Constructor, takes ownership of the allocation
/// \param arr the dynarray to move
constexpr dynarray(dynarray&& arr) noexcept
: _alloc(fennec::move(arr._alloc))
, _size(arr._size) {
arr._size = 0;
}
///
/// \brief Default Destructor, destructs all elements and frees the underlying allocation
constexpr ~dynarray() {
value_t* addr = _alloc.data();
if (addr == nullptr) return;
for(int n = _size; n > 0; --n, ++addr) {
fennec::destruct(addr);
}
}
/// @}
// Assignment ==========================================================================================================
public:
/// \name Properties
/// @{
///
/// \brief Copy Assignment Operator
/// \param arr the array to copy
/// \returns A dynarray after having copied each element of \f$arr\f$
constexpr dynarray& operator=(const dynarray& arr) {
this->clear();
_alloc.creallocate(_size = arr._size);
for (size_t i = 0; i < _size; ++i) {
fennec::construct(&_alloc[i], fennec::copy(arr[i]));
}
return *this;
}
///
/// \brief Move Assignment Operator
/// \param arr the array to move
/// \returns A dynarray after having taken ownership of the contents of \f$arr\f$
constexpr dynarray& operator=(dynarray&& arr) noexcept {
this->clear();
_alloc = fennec::move(arr._alloc);
_size = arr._size;
arr._size = 0;
return *this;
}
///
/// \brief Array Copy Assignment Operator
/// \tparam N the length of the array
/// \param arr the array to copy
/// \returns A dynarray after having copied each element of \f$arr\f$
template<size_t N>
constexpr dynarray& operator=(const TypeT (&arr)[N]) {
this->clear();
_alloc.creallocate(_size = N);
for (size_t i = 0; i < _size; ++i) {
fennec::construct(&_alloc[i], fennec::copy(arr[i]));
}
return *this;
}
///
/// \brief Array Copy Assignment Operator
/// \tparam N the length of the array
/// \param arr the array to copy
/// \returns A dynarray after having moved each element of \f$arr\f$
template<size_t N>
constexpr dynarray& operator=(TypeT (&&arr)[N]) {
this->clear();
_alloc.creallocate(_size = N);
for (size_t i = 0; i < _size; ++i) {
fennec::construct(&_alloc[i], fennec::move(arr[i]));
}
return *this;
}
/// @}
// Properties ==========================================================================================================
public:
/// \name Properties
/// @{
///
/// \returns The size of the dynarray in elements
constexpr size_t size() const {
return _size;
}
///
/// \returns The current capacity, in elements, of the underlying allocation
constexpr size_t capacity() const {
return _alloc.capacity();
}
///
/// \returns True when there are no elements active, otherwise false
constexpr bool is_empty() const {
return _size == 0;
}
/// @}
// Element Access ======================================================================================================
public:
/// \name Access
/// @{
///
/// \brief Array Access Operator
/// \param i The index to access
/// \returns A reference to the element at index \f$i\f$
constexpr TypeT& operator[](size_t i) {
assertd(i < _size, "Array Out of Bounds");
return _alloc[i];
}
///
/// \brief Array Access Operator (const)
/// \param i The index to access
/// \returns A const qualified reference to the element at index \f$i\f$
constexpr const TypeT& operator[](size_t i) const {
assertd(i < _size, "Array Out of Bounds");
return _alloc[i];
}
///
/// \returns Reference to the first element in the dynarray
constexpr TypeT& front() {
return this->operator[](0);
}
///
/// \returns A const-qualified reference to the first element in the dynarray
constexpr const TypeT& front() const {
return this->operator[](0);
}
///
/// \returns A reference to the last element in the dynarray
constexpr TypeT& back() {
return this->operator[](size() - 1);
}
///
/// \returns A const-qualified reference to the last element in the dynarray
constexpr const TypeT& back() const {
return this->operator[](size() - 1);
}
///
/// \returns A pointer to the underlying allocation
constexpr TypeT* data() {
return _alloc.data();
}
///
/// \returns A pointer to the underlying allocation
constexpr const TypeT* data() const {
return _alloc.data();
}
/// @}
// Modifiers ===========================================================================================================
public:
/// \name Modifiers
/// @{
///
/// \brief Move Insertion
/// \param i index to insert at
/// \param val the value to initialize with
constexpr void insert(size_t i, TypeT&& val) {
// Grow if the size has reached the capacity of the allocation
if(_size == capacity()) {
_grow();
}
// Move the data if we are not inserting at the end of the array
if((i = min(i, _size)) < _size) {
fennec::memmove(
(void*)(_alloc.data() + i + 1)
, (void*)(_alloc.data() + i)
, (_size - i) * sizeof(TypeT));
}
// Insert the element
fennec::construct(_alloc.data() + i, fennec::forward<TypeT>(val));
++_size;
}
///
/// \brief Copy Insertion
/// \param i index to insert at
/// \param val the value to initialize with
constexpr void insert(size_t i, const TypeT& val) {
// Grow if the size has reached the capacity of the allocation
if(_size == capacity()) {
_grow();
}
// Move the data if we are not inserting at the end of the array
if((i = min(i, _size)) < _size) {
fennec::memmove(
(void*)(_alloc.data() + i),
(void*)(_alloc.data() + i + 1),
(_size - i) * sizeof(TypeT)
);
}
// Insert the element
fennec::construct(_alloc.data() + i, val);
++_size;
}
///
/// \brief Emplace Insertion
/// \param i index to insert at
/// \param args Arguments to construct with
/// \tparam ArgsT Argument types
template<typename...ArgsT>
constexpr void emplace(size_t i, ArgsT&&...args) {
// Grow if the size has reached the capacity of the allocation
if(_size == capacity()) {
_grow();
}
// Move the data if we are not inserting at the end of the array
if((i = min(i, _size)) < _size) {
fennec::memmove(
(void*)(_alloc.data() + i)
, (void*)(_alloc.data() + i + 1)
, (_size - i) * sizeof(TypeT));
}
// Insert the element
fennec::construct(_alloc.data() + i, fennec::forward<ArgsT>(args)...);
++_size;
}
///
/// \brief Push Back Copy
/// \param val Value to initialize with
constexpr void push_back(const TypeT& val) {
dynarray::insert(_size, val);
}
///
/// \brief Push Back Move
/// \param val Value to initialize with
constexpr void push_back(TypeT&& val) {
dynarray::insert(_size, fennec::forward<TypeT>(val));
}
///
/// \brief Emplace Back
/// \tparam ArgsT Argument Types
/// \param args Arguments to construct with
template<typename...ArgsT>
constexpr void emplace_back(ArgsT...args) {
dynarray::emplace(_size, fennec::forward<ArgsT>(args)...);
}
///
/// \brief Erase last element
constexpr void pop_back() {
fennec::destruct(&_alloc[--_size]);
}
///
/// \brief Resize the dynarray, invoking the default constructor for all new elements
/// \param n The new size in elements
constexpr void resize(size_t n) {
_reduce(n);
_alloc.creallocate(n);
for (size_t i = _size; i < n; ++i) {
fennec::construct(&_alloc[i]);
}
_size = n;
}
///
/// \brief Resize the dynarray, invoking the copy constructor for all new elements
/// \param n The new size in elements
/// \param val The value to fill with
///
/// \details if \f$n\f$ is less than the current size, any elements that would be removed are destructed
constexpr void resize(size_t n, const TypeT& val) {
_reduce(n);
_alloc.creallocate(n);
for (size_t i = _size; i < n; ++i) {
fennec::construct(&_alloc[i], val);
}
_size = n;
}
///
/// \brief Reserve the array, allocating new space without initialization
/// \param n The new capacity in elements
///
/// \details if \f$n\f$ is less than the current size, any elements that would be removed are destructed
constexpr void reserve(size_t n) {
_reduce(n);
_alloc.creallocate(n);
}
///
/// \brief Clears the contents of the dynarray, destructing all elements and releasing the allocation.
constexpr void clear() {
_reduce(0);
_alloc.deallocate();
}
/// @}
// Iteration ===========================================================================================================
public:
/// \name Iteration
/// @{
///
/// \returns A pointer to the first element in the dynarray
constexpr TypeT* begin() { return _alloc.data(); }
///
/// \brief C++ Iterator Specification \f$begin()\f$
/// \returns A const qualified pointer to the first element in the dynarray
constexpr const TypeT* begin() const { return _alloc; }
///
/// \return A pointer to the address after the last element in the dynarray
constexpr TypeT* end() { return begin() + _size; }
///
/// \brief C++ Iterator Specification \f$end()\f$
/// \return A const qualified pointer to the address after the last element in the dynarray
constexpr const TypeT* end() const { return begin() + _size; }
/// @}
// Private Members =====================================================================================================
private:
allocation<value_t, alloc_t> _alloc;
size_t _size;
// Private Helpers =====================================================================================================
private:
// helper to double the capacity of the allocation
constexpr void _grow() {
_alloc.creallocate(_alloc.capacity() * 2);
}
// helper to destruct elements past n
constexpr void _reduce(size_t n) {
while (_size > n) {
fennec::destruct(&_alloc[--_size]);
}
}
};
}
#endif // FENNEC_CONTAINERS_DYNARRAY_H