diff options
Diffstat (limited to 'include/fud_vector.hpp')
-rw-r--r-- | include/fud_vector.hpp | 628 |
1 files changed, 620 insertions, 8 deletions
diff --git a/include/fud_vector.hpp b/include/fud_vector.hpp index 56e1659..f90819a 100644 --- a/include/fud_vector.hpp +++ b/include/fud_vector.hpp @@ -19,42 +19,654 @@ #define FUD_VECTOR_HPP #include "fud_allocator.hpp" +#include "fud_assert.hpp" +#include "fud_config.hpp" +#include "fud_option.hpp" #include "fud_result.hpp" +#include "fud_span.hpp" #include "fud_status.hpp" #include <cstddef> +#include <functional> +#include <new> // IWYU pragma: keep (placement new) namespace fud { template <typename T> class Vector { + static constexpr size_t ElementSize = sizeof(T); + static constexpr size_t Alignment = alignof(T); + public: - static Result<Vector<T>, FudStatus> from(const Vector<T>& rhs); + constexpr Vector() noexcept = default; + constexpr Vector(const Vector<T>& rhs) = delete; + constexpr Vector(Vector<T>&& rhs) noexcept : + m_allocator(rhs.m_allocator), m_data(rhs.m_data), m_length{rhs.m_length}, m_capacity{rhs.m_capacity} + { + rhs.m_allocator = nullptr; + rhs.m_data = nullptr; + rhs.m_length = 0; + rhs.m_capacity = 0; + } + + ~Vector() noexcept + { + static_cast<void>(cleanup()); + } + + Vector& operator=(const Vector<T>& rhs) = delete; + + Vector& operator=(Vector<T>&& rhs) noexcept + { + cleanup(); + m_allocator = rhs.m_allocator; + m_data = rhs.m_data; + m_length = rhs.m_length; + m_capacity = rhs.m_length; + + rhs.m_allocataor = nullptr; + rhs.m_data = nullptr; + rhs.m_length = 0; + rhs.m_capacity = 0; + } + + static Result<Vector<T>, FudStatus> withCapacity(size_t capacity, Allocator* allocator = &globalFudAllocator) + { + Vector<T> output{}; + auto status = initializeWithCapacity(output, capacity, allocator); + if (status != FudStatus::Success) { + return status; + } + return output; + } + + static FudStatus initializeWithCapacity( + Vector<T>& output, + size_t capacity, + Allocator* allocator = &globalFudAllocator) + { + if (output.m_data != nullptr) { + return FudStatus::AlreadyInitialized; + } + + if (allocator == nullptr) { + return FudStatus::NullPointer; + } + + if (capacity > SIZE_MAX / ElementSize) { + return FudStatus::ArgumentInvalid; + } + + size_t requestedSize = capacity * ElementSize; + auto dataPtrResult = allocator->allocate(requestedSize, Alignment); + if (dataPtrResult.isError()) { + return dataPtrResult.getError(); + } + + output.m_allocator = allocator; + output.m_data = static_cast<T*>(dataPtrResult.getOkay()); + output.m_length = 0; + output.m_capacity = capacity; + return FudStatus::Success; + } + + static Result<Vector<T>, FudStatus> withSize(size_t count, Allocator* allocator = &globalFudAllocator) + { + Vector<T> output{}; + auto status = initializeWithCapacity(output, count, allocator); + if (status != FudStatus::Success) { + return status; + } + return output; + } + + static Result<Vector<T>, FudStatus> initializeWithSize( + Vector<T>& output, + size_t count, + Allocator* allocator = &globalFudAllocator) + { + if (output.m_data != nullptr) { + return FudStatus::AlreadyInitialized; + } + + auto status = Vector::initializeWithCapacity(output, count, allocator); + if (status != FudStatus::Success) { + return status; + } + + output.m_length = count; + for (size_t index = 0; index < count; ++index) { + const auto* ptr = new (output.m_data + index) T(); + fudAssert(ptr != nullptr); + } + return output; + } + + template <typename Builder> + static Result<Vector<T>, FudStatus> withSizeFallible( + size_t count, + Builder&& builder, + Allocator* allocator = &globalFudAllocator) + { + Vector<T> output{}; + auto status = initializeWithSizeFallible(output, count, std::forward<Builder>(builder), allocator); + if (status != FudStatus::Success) { + return status; + } + return output; + } + + template <typename Builder> + static Result<Vector<T>, FudStatus> initializeWithSizeFallible( + Vector<T>& output, + size_t count, + Builder&& builder, + Allocator* allocator = &globalFudAllocator) + { + using BuilderResult = decltype(std::forward<Builder>(builder)()); + static_assert(std::is_same_v<BuilderResult, FudStatus>()); + + auto status = Vector::initializeWithCapacity(output, count, allocator); + if (status != FudStatus::Success) { + return status; + } + + output.m_length = count; + + for (size_t index = 0; index < count; ++index) { + auto builderResult{std::forward<Builder>(builder)(output.m_data[index])}; + if (builderResult.isError()) { + return builderResult.takeError(); + } + } + + return output; + } - static Vector<T> move(Vector<T>&& rhs); + static Result<Vector<T>, FudStatus> from(const Vector<T>& rhs, Option<Allocator*> allocatorOption = NullOpt) + { + Allocator* allocator = nullptr; + if (allocatorOption.hasValue()) { + allocator = allocatorOption.value(); + if (allocator == nullptr) { + return FudStatus::NullPointer; + } + } else { + allocator = rhs.allocator; + if (allocator == nullptr) { + return FudStatus::ArgumentInvalid; + } + } + + fudAssert(rhs.m_length <= rhs.m_capacity); + + auto spanResult = rhs.span(); + if (spanResult.isError()) { + return spanResult.takeError(); + } + Vector<T> output{}; + auto status = Vector::initializeFromSpan(output, rhs.m_length, allocator); + if (status != FudStatus::Success) { + return status; + } + return output; + } + + template <size_t Size> + static Result<Vector<T>, FudStatus> from(Span<const T, Size>& rhs, Allocator* allocator) + { + Vector<T> output{}; + auto status = initializeFromSpan(output, rhs, allocator); + if (status != FudStatus::Success) { + return status; + } + return output; + } + + template <size_t Size> + static FudStatus initializeFromSpan(Vector<T>& output, Span<const T, Size>& rhs, Allocator* allocator) + { + auto status = Vector::initializeWithCapacity(output, rhs.size(), allocator); + if (status != FudStatus::Success) { + return status; + } + output.m_length = rhs.m_length; + for (size_t index = 0; index < output.m_length; ++index) { + output.m_data[index] = rhs[index]; + } + } + + static Vector<T> move(Vector<T>&& rhs) noexcept + { + return Vector<T>{std::move(rhs)}; + } FudStatus copy(const Vector<T>& rhs); FudStatus take(Vector<T>&& rhs); - [[nodiscard]] size_t size() const { + [[nodiscard]] size_t size() const + { return m_length; } - [[nodiscard]] size_t capacity() const { + [[nodiscard]] size_t capacity() const + { return m_capacity; } - FudStatus reserve(); + Result<Span<const T>, FudStatus> span() const + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + return Span{m_data, m_length}; + } + + Result<Span<T>, FudStatus> span() + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + return Span{m_data, m_length}; + } + + Result<Span<const T>, FudStatus> span(size_t count) const + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + if (count > m_length) { + return FudStatus::ArgumentInvalid; + } + return Span{m_data, count}; + } + + Result<Span<T>, FudStatus> span(size_t count) + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + if (count > m_length) { + return FudStatus::ArgumentInvalid; + } + return Span{m_data, count}; + } + + Result<Span<const T>, FudStatus> span(size_t start, size_t count) const + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + if (SIZE_MAX - start < m_length || start + count > m_length) { + return FudStatus::ArgumentInvalid; + } + return Span{m_data + start, count}; + } + + Result<Span<T>, FudStatus> span(size_t start, size_t count) + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + if (SIZE_MAX - start < m_length || start + count > m_length) { + return FudStatus::ArgumentInvalid; + } + return Span{m_data + start, count}; + } + + FudStatus reserve(size_t count) + { + if (count <= m_capacity) { + return FudStatus::Success; + } + + if (m_allocator == nullptr) { + return FudStatus::ObjectInvalid; + } + + if (count > SIZE_MAX / ElementSize) { + return FudStatus::ArgumentInvalid; + } + + size_t requestedSize = count * ElementSize; + auto dataPtrResult = m_allocator->allocate(requestedSize, Alignment); + if (dataPtrResult.isError()) { + return dataPtrResult.takeError(); + } + + auto* dataPtr = static_cast<T*>(dataPtrResult.takeOkay()); + for (size_t index = 0; index < m_length; ++index) { + const auto* ptr = new (dataPtr + index) T(std::move(m_data[index])); + fudAssert(ptr != nullptr); + m_data[index].~T(); + } + + m_data = dataPtr; + m_capacity = count; + + return FudStatus::Success; + } + + FudStatus resize(size_t count) + { + if (count == m_length) { + return FudStatus::Success; + } + + if (m_allocator == nullptr) { + return FudStatus::ObjectInvalid; + } - FudStatus resize(); + if (count < m_length) { + for (size_t index = count; index < m_length; ++index) { + m_data[index].~T(); + } + m_length = count; + return FudStatus::Success; + } - FudStatus clear(); + auto reserveStatus = reserve(count); + if (reserveStatus != FudStatus::Success) { + return reserveStatus; + } - // FudResult at(); + for (size_t index = m_length; index < count; ++index) { + const auto* ptr = new (m_data + index) T(); + fudAssert(ptr != nullptr); + } + + m_length = count; + return FudStatus::Success; + } + + FudStatus clear() + { + if (m_allocator == nullptr || m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + for (size_t index = 0; index < m_length; ++index) { + m_data[index].~T(); + } + m_length = 0; + return FudStatus::Success; + } + + Result<std::reference_wrapper<T>, FudStatus> get(size_t index) + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + if (index >= m_length) { + return FudStatus::IndexInvalid; + } + return std::ref(m_data[index]); + } + + Result<const std::reference_wrapper<const T>, FudStatus> ref(size_t index) const + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + if (index >= m_length) { + return FudStatus::IndexInvalid; + } + return std::cref(m_data[index]); + } + + constexpr Option<T&> front() + { + if (m_length > 0) { + fudAssert(m_data != nullptr); + return m_data[0]; + } + return NullOpt; + } + + constexpr Option<const T&> front() const + { + if (m_length > 0) { + fudAssert(m_data != nullptr); + return m_data[0]; + } + return NullOpt; + } + + constexpr Option<T&> back() + { + if (m_length > 0) { + fudAssert(m_data != nullptr); + return m_data[m_length - 1]; + } + return NullOpt; + } + + constexpr Option<const T&> back() const + { + if (m_length > 0) { + return m_data[m_length - 1]; + } + return NullOpt; + } + + constexpr T* data() noexcept + { + return m_data; + } + + constexpr const T* data() const noexcept + { + return m_data; + } + + constexpr T* begin() noexcept + { + return m_data; + } + + constexpr const T* begin() const noexcept + { + return m_data; + } + + constexpr T* end() noexcept + { + return m_data + m_length; + } + + constexpr const T* end() const noexcept + { + return m_data + m_length; + } + + constexpr T& operator[](size_t index) + { + if constexpr (fudBoundsChecking) { + fudAssert(m_data != nullptr); + fudAssert(index < m_length); + } + return m_data[index]; + } + + constexpr const T& operator[](size_t index) const + { + if constexpr (fudBoundsChecking) { + fudAssert(m_data != nullptr); + fudAssert(index < m_length); + } + return m_data[index]; + } + + FudStatus pushBack(const T& value) + { + if (m_length == m_capacity) { + auto status = grow(); + if (status != FudStatus::Success) { + return status; + } + } + const auto* ptr = new (m_data + m_length) T(value); + fudAssert(ptr != nullptr); + m_length++; + return FudStatus::Success; + } + + FudStatus pushBack(T&& value) + { + if (m_length == m_capacity) { + auto status = grow(); + if (status != FudStatus::Success) { + return status; + } + } + const auto* ptr = new (m_data + m_length) T(std::move(value)); + fudAssert(ptr != nullptr); + m_length++; + return FudStatus::Success; + } + + Result<T, FudStatus> popBack() + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + if (m_length == 0) { + return FudStatus::Empty; + } + auto result{std::move(m_data[m_length - 1])}; + m_length--; + m_data[m_length].~T(); + return result; + } + + FudStatus eraseBack() + { + if (m_data == nullptr) { + return FudStatus::ObjectInvalid; + } + if (m_length == 0) { + return FudStatus::Empty; + } + m_length--; + m_data[m_length].~T(); + return FudStatus::Success; + } + + FudStatus insert(size_t index, const T& value) + { + if (index > m_length) { + return FudStatus::IndexInvalid; + } + if (index == m_length) { + return pushBack(value); + } + if (m_length == m_capacity) { + auto status = grow(); + if (status != FudStatus::Success) { + return status; + } + } + + const auto* ptr = new (m_data + m_length) T(std::move(m_data[m_length - 1])); + fudAssert(ptr != nullptr); + + for (size_t backIndex = m_length - 1; backIndex > index; --backIndex) { + m_data[backIndex] = std::move(m_data[backIndex - 1]); + } + m_data[index] = value; + m_length++; + return FudStatus::Success; + } + + FudStatus insert(size_t index, T&& value) + { + if (index > m_length) { + return FudStatus::IndexInvalid; + } + if (index == m_length) { + return pushBack(std::move(value)); + } + if (m_length == m_capacity) { + auto status = grow(); + if (status != FudStatus::Success) { + return status; + } + } + + const auto* ptr = new (m_data + m_length) T(std::move(m_data[m_length - 1])); + fudAssert(ptr != nullptr); + + for (size_t backIndex = m_length - 1; backIndex > index; --backIndex) { + m_data[backIndex] = std::move(m_data[backIndex - 1]); + } + m_data[index] = std::move(value); + m_length++; + return FudStatus::Success; + } + + FudStatus erase(size_t index) + { + if (index >= m_length) { + return FudStatus::IndexInvalid; + } + + m_data[index].~T(); + for (size_t fwdIndex = index; fwdIndex + 1 < m_length; fwdIndex++) + { + m_data[fwdIndex] = std::move(m_data[fwdIndex + 1]); + } + m_data[m_length - 1].~T(); + m_length--; + return FudStatus::Success; + } private: + FudStatus grow() + { + // See https://github.com/facebook/folly/blob/main/folly/docs/FBVector.md + size_t additional = m_capacity < 2 ? 1 : m_capacity / 2; + if (SIZE_MAX - additional * ElementSize < m_capacity * ElementSize) { + additional = SIZE_MAX - m_capacity * ElementSize / 2; + } + while (additional > 0) { + auto reserveStatus = reserve(additional + m_capacity); + if (reserveStatus == FudStatus::Success) { + break; + } + if (reserveStatus == FudStatus::AllocFailure) { + additional /= 2; + } else { + return reserveStatus; + } + } + + if (m_length == m_capacity) { + return FudStatus::AllocFailure; + } + + return FudStatus::Success; + } + + FudStatus cleanup() noexcept + { + auto status = clear(); + + if (m_data != nullptr && m_allocator != nullptr) { + auto deallocStatus = m_allocator->deallocate(m_data, m_capacity); + if (status == FudStatus::Success) { + status = deallocStatus; + } + } + + m_allocator = nullptr; + m_data = nullptr; + m_length = 0; + m_capacity = 0; + return status; + } + Allocator* m_allocator{&globalFudAllocator}; + T* m_data{nullptr}; size_t m_length{0}; size_t m_capacity{0}; }; |