366 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			366 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
 | 
						|
#pragma once
 | 
						|
 | 
						|
#if !defined(MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED)
 | 
						|
#define MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED 1
 | 
						|
 | 
						|
#include <cstdint>
 | 
						|
#include <functional>
 | 
						|
#include <utility>
 | 
						|
#include "../debug/assert.hpp"
 | 
						|
#include "../util/concepts.hpp"
 | 
						|
#include "../util/traits.hpp"
 | 
						|
 | 
						|
namespace mijin
 | 
						|
{
 | 
						|
 | 
						|
//
 | 
						|
// public defines
 | 
						|
//
 | 
						|
 | 
						|
//
 | 
						|
// public constants
 | 
						|
//
 | 
						|
 | 
						|
//
 | 
						|
// public types
 | 
						|
//
 | 
						|
 | 
						|
namespace impl
 | 
						|
{
 | 
						|
template<typename T>
 | 
						|
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<typename... TArgs>
 | 
						|
    constexpr void emplace(TArgs&&... args) noexcept
 | 
						|
    {
 | 
						|
        MIJIN_ASSERT(!used, "Attempting to emplace in an already set OptionalStorage!");
 | 
						|
        used = 1;
 | 
						|
        ::new (data) T(std::forward<TArgs>(args)...);
 | 
						|
    }
 | 
						|
 | 
						|
    void clear()
 | 
						|
    {
 | 
						|
        MIJIN_ASSERT(used, "Attempting to clear an empty OptionalStorage!");
 | 
						|
        get().~T();
 | 
						|
        used = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    [[nodiscard]] constexpr T& get() noexcept { return *reinterpret_cast<T*>(data); }
 | 
						|
    [[nodiscard]] constexpr const T& get() const noexcept { return *reinterpret_cast<const T*>(data); }
 | 
						|
};
 | 
						|
 | 
						|
template<pointer_type T>
 | 
						|
struct OptionalStorage<T>
 | 
						|
{
 | 
						|
    static constexpr T invalidPointer() noexcept { return reinterpret_cast<T>(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<reference_type T>
 | 
						|
struct OptionalStorage<T>
 | 
						|
{
 | 
						|
    using pointer_t = std::remove_reference_t<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<typename TValue>
 | 
						|
class Optional
 | 
						|
{
 | 
						|
public:
 | 
						|
    using value_t = TValue;
 | 
						|
private:
 | 
						|
    impl::OptionalStorage<TValue> 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<typename TOther>
 | 
						|
    [[nodiscard]]
 | 
						|
    constexpr bool operator ==(const Optional<TOther>& other) const noexcept;
 | 
						|
    template<typename T>
 | 
						|
    [[nodiscard]]
 | 
						|
    constexpr bool operator ==(const T& value) const noexcept;
 | 
						|
    template<typename T>
 | 
						|
    [[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<TValue>& operator*() noexcept { return get(); }
 | 
						|
    [[nodiscard]]
 | 
						|
    constexpr const std::remove_reference_t<TValue>& operator*() const noexcept { return get(); }
 | 
						|
    [[nodiscard]]
 | 
						|
    constexpr std::remove_reference_t<TValue>* operator->() noexcept { return &get(); }
 | 
						|
    [[nodiscard]]
 | 
						|
    constexpr const std::remove_reference_t<TValue>* operator->() const noexcept { return &get(); }
 | 
						|
public:
 | 
						|
    template<typename... Types>
 | 
						|
    void emplace(Types&&... params) noexcept;
 | 
						|
public:
 | 
						|
    [[nodiscard]] inline std::remove_reference_t<TValue>& get() noexcept;
 | 
						|
    [[nodiscard]] inline const std::remove_reference_t<TValue>& get() const noexcept;
 | 
						|
    [[nodiscard]] constexpr bool empty() const noexcept { return storage_.empty(); }
 | 
						|
    inline void reset() noexcept;
 | 
						|
 | 
						|
 | 
						|
    template<typename TCallable, typename TErrorCallable = int>
 | 
						|
    auto then(TCallable&& onSuccess, TErrorCallable&& onError = 0)
 | 
						|
    {
 | 
						|
        return thenImpl(*this, std::forward<TCallable>(onSuccess), std::forward<TErrorCallable>(onError));
 | 
						|
    }
 | 
						|
 | 
						|
    template<typename TCallable, typename TErrorCallable = int>
 | 
						|
    auto then(TCallable&& onSuccess, TErrorCallable&& onError = 0) const
 | 
						|
    {
 | 
						|
        return thenImpl(*this, std::forward<TCallable>(onSuccess), std::forward<TErrorCallable>(onError));
 | 
						|
    }
 | 
						|
private:
 | 
						|
    template<typename TSelf, typename TCallable, typename TErrorCallable = int>
 | 
						|
    static auto thenImpl(TSelf&& self, TCallable&& onSuccess, TErrorCallable&& onError = 0)
 | 
						|
    {
 | 
						|
        using result_t = std::invoke_result_t<TCallable, std::add_rvalue_reference_t<TValue>>;
 | 
						|
 | 
						|
        if constexpr (std::is_same_v<result_t, void>)
 | 
						|
        {
 | 
						|
            if (!self.empty())
 | 
						|
            {
 | 
						|
                std::invoke(std::forward<TCallable>(onSuccess), self.get());
 | 
						|
            }
 | 
						|
            else if constexpr (!std::is_same_v<TErrorCallable, int>)
 | 
						|
            {
 | 
						|
                std::invoke(std::forward<TErrorCallable>(onError));
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else if constexpr (requires(result_t obj) { {obj == NULL_OPTIONAL} -> std::convertible_to<bool>; })
 | 
						|
        {
 | 
						|
            if (!self.empty())
 | 
						|
            {
 | 
						|
                return std::invoke(std::forward<TCallable>(onSuccess), self.get());
 | 
						|
            }
 | 
						|
            else
 | 
						|
            {
 | 
						|
                if constexpr (!std::is_same_v<TErrorCallable, int>)
 | 
						|
                {
 | 
						|
                    std::invoke(std::forward<TErrorCallable>(onError));
 | 
						|
                }
 | 
						|
                return result_t(NULL_OPTIONAL);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        else
 | 
						|
        {
 | 
						|
            static_assert(always_false_v<TCallable>, "Callable must produce void or optional type.");
 | 
						|
        }
 | 
						|
    }
 | 
						|
};
 | 
						|
 | 
						|
//
 | 
						|
// public functions
 | 
						|
//
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
Optional<TValue>::Optional(const Optional& other) noexcept
 | 
						|
{
 | 
						|
    if (other) {
 | 
						|
        emplace(other.get());
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
Optional<TValue>::Optional(Optional&& other) noexcept
 | 
						|
{
 | 
						|
    if (other)
 | 
						|
    {
 | 
						|
        if constexpr (!std::is_reference_v<TValue>) {
 | 
						|
            emplace(std::move(other.get()));
 | 
						|
        }
 | 
						|
        else {
 | 
						|
            emplace(other.get());
 | 
						|
        }
 | 
						|
        other.reset();
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
Optional<TValue>::Optional(TValue value) noexcept
 | 
						|
{
 | 
						|
    if constexpr (std::is_reference_v<TValue>) {
 | 
						|
        emplace(value);
 | 
						|
    }
 | 
						|
    else {
 | 
						|
        emplace(std::move(value));
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
Optional<TValue>::~Optional() noexcept
 | 
						|
{
 | 
						|
    reset();
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
auto Optional<TValue>::operator =(const Optional& other) noexcept -> Optional&
 | 
						|
{
 | 
						|
    if (&other == this) {
 | 
						|
        return *this;
 | 
						|
    }
 | 
						|
    reset();
 | 
						|
    if (other) {
 | 
						|
        emplace(other.get());
 | 
						|
    }
 | 
						|
    return *this;
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
auto Optional<TValue>::operator =(Optional&& other) noexcept -> Optional&
 | 
						|
{
 | 
						|
    if (&other == this) {
 | 
						|
        return *this;
 | 
						|
    }
 | 
						|
    reset();
 | 
						|
    if (other)
 | 
						|
    {
 | 
						|
        emplace(std::move(other.get()));
 | 
						|
        other.reset();
 | 
						|
    }
 | 
						|
    return *this;
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
auto Optional<TValue>::operator =(TValue value) noexcept -> Optional&
 | 
						|
{
 | 
						|
    if constexpr (std::is_reference_v<TValue>) {
 | 
						|
        emplace(value);
 | 
						|
    }
 | 
						|
    else {
 | 
						|
        emplace(std::move(value));
 | 
						|
    }
 | 
						|
    return *this;
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
auto Optional<TValue>::operator =(NullOptional) noexcept -> Optional&
 | 
						|
{
 | 
						|
    reset();
 | 
						|
    return *this;
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
template<typename TOther>
 | 
						|
constexpr bool Optional<TValue>::operator ==(const Optional<TOther>& other) const noexcept
 | 
						|
{
 | 
						|
    if (empty())
 | 
						|
    {
 | 
						|
        return other.empty();
 | 
						|
    }
 | 
						|
    if (!other.empty())
 | 
						|
    {
 | 
						|
        return get() == other.get();
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
template<typename T>
 | 
						|
constexpr bool Optional<TValue>::operator ==(const T& value) const noexcept
 | 
						|
{
 | 
						|
    if (empty())
 | 
						|
    {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
    return get() == value;
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
template<typename... Types>
 | 
						|
void Optional<TValue>::emplace(Types&&... params) noexcept
 | 
						|
{
 | 
						|
    reset();
 | 
						|
    storage_.emplace(std::forward<Types>(params)...);
 | 
						|
    MIJIN_ASSERT(!empty(), "Something is wrong.");
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
inline std::remove_reference_t<TValue>& Optional<TValue>::get() noexcept
 | 
						|
{
 | 
						|
    MIJIN_ASSERT(!empty(), "Attempting to fetch value from empty Optional!");
 | 
						|
    return storage_.get();
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
inline const std::remove_reference_t<TValue>& Optional<TValue>::get() const noexcept
 | 
						|
{
 | 
						|
    MIJIN_ASSERT(!empty(), "Attempting to fetch value from empty Optional!");
 | 
						|
    return storage_.get();
 | 
						|
}
 | 
						|
 | 
						|
template<typename TValue>
 | 
						|
inline void Optional<TValue>::reset() noexcept
 | 
						|
{
 | 
						|
    if (empty()) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    storage_.clear();
 | 
						|
}
 | 
						|
 | 
						|
} // namespace mijin
 | 
						|
 | 
						|
#endif // !defined(MIJIN_CONTAINER_OPTIONAL_HPP_INCLUDED)
 |