/* * libfud * Copyright 2024 Dominick Allen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FUD_VECTOR_HPP #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 #include #include // IWYU pragma: keep (placement new) namespace fud { template class Vector { static constexpr size_t ElementSize = sizeof(T); static constexpr size_t Alignment = alignof(T); public: constexpr Vector() noexcept = default; constexpr explicit Vector(Allocator& allocator) noexcept : m_allocator{&allocator} {} constexpr Vector(const Vector& rhs) = delete; constexpr Vector(Vector&& 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(cleanup()); } Vector& operator=(const Vector& rhs) = delete; Vector& operator=(Vector&& rhs) noexcept { if (&rhs == this) { return *this; } static_cast(cleanup()); m_allocator = rhs.m_allocator; m_data = rhs.m_data; m_length = rhs.m_length; m_capacity = rhs.m_length; rhs.m_allocator = nullptr; rhs.m_data = nullptr; rhs.m_length = 0; rhs.m_capacity = 0; return *this; } static constexpr Vector NullVector() noexcept { Vector output{}; output.m_allocator = &globalNullAllocator; return output; } static Result, FudStatus> withCapacity(size_t capacity, Allocator* allocator = &globalFudAllocator) { Vector output{}; auto status = initializeWithCapacity(output, capacity, allocator); if (status != FudStatus::Success) { return status; } return output; } static FudStatus initializeWithCapacity( Vector& 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; // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) output.m_data = reinterpret_cast(dataPtrResult.getOkay()); output.m_length = 0; output.m_capacity = capacity; return FudStatus::Success; } static Result, FudStatus> withSize(size_t count, Allocator* allocator = &globalFudAllocator) { Vector output{}; auto status = initializeWithSize(output, count, allocator); if (status != FudStatus::Success) { return FudError{status}; } return Okay>{std::move(output)}; } static FudStatus initializeWithSize( Vector& 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 FudStatus::Success; } template static Result, FudStatus> withSizeFallible( size_t count, Builder&& builder, Allocator* allocator = &globalFudAllocator) { Vector output{}; auto status = initializeWithSizeFallible(output, count, std::forward(builder), allocator); if (status != FudStatus::Success) { return FudError{status}; } return Okay>{std::move(output)}; } template static FudStatus initializeWithSizeFallible( Vector& output, size_t count, Builder&& builder, Allocator* allocator = &globalFudAllocator) { // using BuilderResult = decltype(std::forward(builder)(T{})); // static_assert(std::is_same_v); 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 builderStatus{std::forward(builder)(output.m_data[index])}; if (builderStatus != FudStatus::Success) { return builderStatus; } } return FudStatus::Success; } static Result, FudStatus> from(const Vector& rhs, Option allocatorOption = NullOpt) { Allocator* allocator = nullptr; if (allocatorOption.hasValue()) { allocator = allocatorOption.value(); if (allocator == nullptr) { return FudStatus::NullPointer; } } else { allocator = rhs.m_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 output{}; auto status = Vector::initializeFromSpan(output, rhs.m_length, allocator); if (status != FudStatus::Success) { return status; } return output; } template static Result, FudStatus> from(Span& rhs, Allocator* allocator) { Vector output{}; auto status = initializeFromSpan(output, rhs, allocator); if (status != FudStatus::Success) { return status; } return output; } template static FudStatus initializeFromSpan(Vector& output, Span& 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 move(Vector&& rhs) noexcept { return Vector{std::move(rhs)}; } FudStatus copy(const Vector& rhs); FudStatus take(Vector&& rhs); [[nodiscard]] size_t size() const { return m_length; } [[nodiscard]] size_t capacity() const { return m_capacity; } Result, FudStatus> span() const { using RetType = Result, FudStatus>; if (m_data == nullptr) { return RetType::error(FudStatus::ObjectInvalid); } return RetType::okay(Span{m_data, m_length}); } Result, FudStatus> span() { using RetType = Result, FudStatus>; if (m_data == nullptr) { return RetType::error(FudStatus::ObjectInvalid); } return RetType::okay(Span{m_data, m_length}); } Result, FudStatus> span(size_t count) const { using RetType = Result, FudStatus>; if (m_data == nullptr) { return RetType::error(FudStatus::ObjectInvalid); } if (count > m_length) { return RetType::error(FudStatus::ArgumentInvalid); } return RetType::okay(Span{m_data, count}); } Result, FudStatus> span(size_t count) { using RetType = Result, FudStatus>; if (m_data == nullptr) { return RetType::error(FudStatus::ObjectInvalid); } if (count > m_length) { return RetType::error(FudStatus::ArgumentInvalid); } return RetType::okay(Span{m_data, count}); } Result, FudStatus> span(size_t start, size_t count) const { using RetType = Result, FudStatus>; if (m_data == nullptr) { return RetType::error(FudStatus::ObjectInvalid); } if (SIZE_MAX - start < m_length || start + count > m_length) { return RetType::error(FudStatus::ArgumentInvalid); } return RetType::okay(Span{m_data + start, count}); } Result, FudStatus> span(size_t start, size_t count) { using RetType = Result, FudStatus>; if (m_data == nullptr) { return RetType::error(FudStatus::ObjectInvalid); } if (SIZE_MAX - start < m_length || start + count > m_length) { return RetType::error(FudStatus::ArgumentInvalid); } return RetType::okay(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(); } // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) auto* dataPtr = reinterpret_cast(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(); } auto status = FudStatus::Success; if (m_capacity > 0) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) m_allocator->deallocate(reinterpret_cast(m_data), m_capacity); } m_data = dataPtr; m_capacity = count; return status; } FudStatus resize(size_t count) { if (count == m_length) { return FudStatus::Success; } if (m_allocator == nullptr) { return FudStatus::ObjectInvalid; } if (count < m_length) { for (size_t index = count; index < m_length; ++index) { m_data[index].~T(); } m_length = count; return FudStatus::Success; } auto reserveStatus = reserve(count); if (reserveStatus != FudStatus::Success) { return reserveStatus; } 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) { if (m_length > 0) { return FudStatus::ObjectInvalid; } return FudStatus::Success; } for (size_t index = 0; index < m_length; ++index) { m_data[index].~T(); } m_length = 0; return FudStatus::Success; } Result, FudStatus> get(size_t index) { using RetType = Result, FudStatus>; if (m_data == nullptr) { return RetType::error(FudStatus::ObjectInvalid); } if (index >= m_length) { return RetType::error(FudStatus::IndexInvalid); } return RetType::okay(std::ref(m_data[index])); } Result, FudStatus> ref(size_t index) const { using RetType = Result, FudStatus>; if (m_data == nullptr) { return RetType::error(FudStatus::ObjectInvalid); } if (index >= m_length) { return RetType::error(FudStatus::IndexInvalid); } return RetType::okay(std::cref(m_data[index])); } constexpr Option front() { if (m_length > 0) { fudAssert(m_data != nullptr); return m_data[0]; } return NullOpt; } constexpr Option front() const { if (m_length > 0) { fudAssert(m_data != nullptr); return m_data[0]; } return NullOpt; } constexpr Option back() { if (m_length > 0) { fudAssert(m_data != nullptr); return m_data[m_length - 1]; } return NullOpt; } constexpr Option 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 popBack() { using RetType = Result; if (m_data == nullptr) { return RetType::error(FudStatus::ObjectInvalid); } if (m_length == 0) { return RetType::error(FudStatus::Empty); } auto result{RetType::okay({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; } template FudStatus extend(Span fixedSpan) { if (fixedSpan.data() == nullptr) { return FudStatus::NullPointer; } if (std::numeric_limits::max() - Size < m_length) { return FudStatus::Failure; } if (m_length + Size > m_capacity) { size_t currentLength = m_length; auto status = resize(m_length + Size); m_length = currentLength; if (status != FudStatus::Success) { return status; } } for (size_t spanIndex = 0; spanIndex < Size; ++spanIndex) { const auto* ptr = new (m_data + m_length) T(fixedSpan[spanIndex]); fudAssert(ptr != nullptr); m_length++; } return FudStatus::Success; } FudStatus extend(Span span) { if (span.data() == nullptr) { return FudStatus::NullPointer; } if (std::numeric_limits::max() - span.size() < m_length) { return FudStatus::Failure; } if (m_length + span.size() > m_capacity) { size_t currentLength = m_length; auto status = resize(m_length + span.size()); m_length = currentLength; if (status != FudStatus::Success) { return status; } } for (size_t spanIndex = 0; spanIndex < span.size(); ++spanIndex) { const auto* ptr = new (m_data + m_length) T(span[spanIndex]); fudAssert(ptr != nullptr); 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; constexpr auto maxSize = std::numeric_limits::max(); if (maxSize - additional * ElementSize < m_capacity * ElementSize) { additional = maxSize - 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) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) m_allocator->deallocate(reinterpret_cast(m_data), m_capacity); } 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}; }; } // namespace fud #endif