#pragma once #if !defined(MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED) #define MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED 1 #include #include #include #include "../debug/assert.hpp" #include "../util/concepts.hpp" #include "../util/traits.hpp" namespace mijin { // // public defines // // // public constants // // // public types // namespace impl { template struct OptionalStorage { alignas(T) std::uint8_t data[sizeof(T)]; // NOLINT(modernize-avoid-c-arrays,cppcoreguidelines-avoid-c-arrays) std::uint8_t used = 0; [[nodiscard]] constexpr bool empty() const noexcept { return !used; } // NOLINT(clang-analyzer-core.uninitialized.UndefReturn) template constexpr void emplace(TArgs&&... args) noexcept { MIJIN_ASSERT(!used, "Attempting to emplace in an already set OptionalStorage!"); used = 1; ::new (data) T(std::forward(args)...); } void clear() { MIJIN_ASSERT(used, "Attempting to clear an empty OptionalStorage!"); get().~T(); used = 0; } [[nodiscard]] constexpr T& get() noexcept { return *reinterpret_cast(data); } [[nodiscard]] constexpr const T& get() const noexcept { return *reinterpret_cast(data); } }; template struct OptionalStorage { static constexpr T invalidPointer() noexcept { return reinterpret_cast(1); } T ptr = invalidPointer(); [[nodiscard]] constexpr bool empty() const noexcept { return ptr == invalidPointer(); } void emplace(T value) noexcept { ptr = value; } void clear() { ptr = invalidPointer(); } [[nodiscard]] T& get() noexcept { return ptr; } [[nodiscard]] const T& get() const noexcept { return ptr; } }; template struct OptionalStorage { using pointer_t = std::remove_reference_t*; pointer_t ptr = nullptr; [[nodiscard]] constexpr bool empty() const noexcept { return ptr == nullptr; } void emplace(T value) noexcept { ptr = &value; } void clear() { ptr = nullptr; } T get() noexcept { return *ptr; } const T get() const noexcept { return *ptr; } // NOLINT(readability-const-return-type) T already is a reference }; } struct NullOptional {}; static constexpr NullOptional NULL_OPTIONAL; template class Optional { public: using value_t = TValue; private: impl::OptionalStorage storage_ = {}; public: constexpr Optional() = default; constexpr Optional(NullOptional) noexcept {} inline Optional(const Optional& other) noexcept; inline Optional(Optional&& other) noexcept; inline Optional(TValue value) noexcept; inline ~Optional() noexcept; public: Optional& operator =(const Optional& other) noexcept; Optional& operator =(Optional&& other) noexcept; Optional& operator =(TValue value) noexcept; Optional& operator =(NullOptional) noexcept; public: [[nodiscard]] constexpr bool operator ==(NullOptional) const noexcept { return empty(); } template [[nodiscard]] constexpr bool operator ==(const Optional& other) const noexcept; template [[nodiscard]] constexpr bool operator ==(const T& value) const noexcept; template [[nodiscard]] constexpr bool operator !=(const T& value) const noexcept { return !(*this == value); } [[nodiscard]] constexpr explicit operator bool() const noexcept { return !empty(); } [[nodiscard]] constexpr bool operator !() const noexcept { return empty(); } [[nodiscard]] constexpr std::remove_reference_t& operator*() noexcept { return get(); } [[nodiscard]] constexpr const std::remove_reference_t& operator*() const noexcept { return get(); } [[nodiscard]] constexpr std::remove_reference_t* operator->() noexcept { return &get(); } [[nodiscard]] constexpr const std::remove_reference_t* operator->() const noexcept { return &get(); } public: template void emplace(Types&&... params) noexcept; public: [[nodiscard]] inline std::remove_reference_t& get() noexcept; [[nodiscard]] inline const std::remove_reference_t& get() const noexcept; [[nodiscard]] constexpr bool empty() const noexcept { return storage_.empty(); } inline void reset() noexcept; template auto then(TCallable&& onSuccess, TErrorCallable&& onError = 0) { return thenImpl(*this, std::forward(onSuccess), std::forward(onError)); } template auto then(TCallable&& onSuccess, TErrorCallable&& onError = 0) const { return thenImpl(*this, std::forward(onSuccess), std::forward(onError)); } private: template static auto thenImpl(TSelf&& self, TCallable&& onSuccess, TErrorCallable&& onError = 0) { using result_t = std::invoke_result_t>; if constexpr (std::is_same_v) { if (!self.empty()) { std::invoke(std::forward(onSuccess), self.get()); } else if constexpr (!std::is_same_v) { std::invoke(std::forward(onError)); } } else if constexpr (requires(result_t obj) { {obj == NULL_OPTIONAL} -> std::convertible_to; }) { if (!self.empty()) { return std::invoke(std::forward(onSuccess), self.get()); } else { if constexpr (!std::is_same_v) { std::invoke(std::forward(onError)); } return result_t(NULL_OPTIONAL); } } else { static_assert(always_false_v, "Callable must produce void or optional type."); } } }; // // public functions // template Optional::Optional(const Optional& other) noexcept { if (other) { emplace(other.get()); } } template Optional::Optional(Optional&& other) noexcept { if (other) { if constexpr (!std::is_reference_v) { emplace(std::move(other.get())); } else { emplace(other.get()); } other.reset(); } } template Optional::Optional(TValue value) noexcept { if constexpr (std::is_reference_v) { emplace(value); } else { emplace(std::move(value)); } } template Optional::~Optional() noexcept { reset(); } template auto Optional::operator =(const Optional& other) noexcept -> Optional& { if (&other == this) { return *this; } reset(); if (other) { emplace(other.get()); } return *this; } template auto Optional::operator =(Optional&& other) noexcept -> Optional& { if (&other == this) { return *this; } reset(); if (other) { emplace(std::move(other.get())); other.reset(); } return *this; } template auto Optional::operator =(TValue value) noexcept -> Optional& { if constexpr (std::is_reference_v) { emplace(value); } else { emplace(std::move(value)); } return *this; } template auto Optional::operator =(NullOptional) noexcept -> Optional& { reset(); return *this; } template template constexpr bool Optional::operator ==(const Optional& other) const noexcept { if (empty()) { return other.empty(); } if (!other.empty()) { return get() == other.get(); } return false; } template template constexpr bool Optional::operator ==(const T& value) const noexcept { if (empty()) { return false; } return get() == value; } template template void Optional::emplace(Types&&... params) noexcept { reset(); storage_.emplace(std::forward(params)...); MIJIN_ASSERT(!empty(), "Something is wrong."); } template inline std::remove_reference_t& Optional::get() noexcept { MIJIN_ASSERT(!empty(), "Attempting to fetch value from empty Optional!"); return storage_.get(); } template inline const std::remove_reference_t& Optional::get() const noexcept { MIJIN_ASSERT(!empty(), "Attempting to fetch value from empty Optional!"); return storage_.get(); } template inline void Optional::reset() noexcept { if (empty()) { return; } storage_.clear(); } } // namespace mijin #endif // !defined(MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED)