summaryrefslogtreecommitdiff
path: root/include/fud_vector.hpp
diff options
context:
space:
mode:
authorDominick Allen <djallen@librehumanitas.org>2024-10-21 12:49:43 -0500
committerDominick Allen <djallen@librehumanitas.org>2024-10-21 12:49:43 -0500
commitb2dbcb55e2832c373fecb4033a3ed77e5dbc77aa (patch)
tree1f294fcf1d85a02db86de3eea2b03393fd89ca5a /include/fud_vector.hpp
parent6a27a2a4032e88fa9154ef0f0741edc584f7a701 (diff)
Add vector and option.
Diffstat (limited to 'include/fud_vector.hpp')
-rw-r--r--include/fud_vector.hpp628
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};
};