/* * Copyright (c) 2018-2021, Andreas Kling * Copyright (c) 2021, Daniel Bertalan * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include namespace AK { namespace Detail { template struct ConditionallyResultType; template struct ConditionallyResultType { using Type = typename T::ResultType; }; template struct ConditionallyResultType { using Type = T; }; } template using ConditionallyResultType = typename Detail::ConditionallyResultType::Type; // NOTE: If you're here because of an internal compiler error in GCC 10.3.0+, // it's because of the following bug: // // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=96745 // // Make sure you didn't accidentally make your destructor private before // you start bug hunting. :^) template class Optional; struct OptionalNone { explicit OptionalNone() = default; }; template> requires(!IsLvalueReference) class [[nodiscard]] OptionalBase { public: using ValueType = T; template V> Self& operator=(V) { static_cast(*this).clear(); return static_cast(*this); } [[nodiscard]] ALWAYS_INLINE T* ptr() & { return static_cast(*this).has_value() ? __builtin_launder(reinterpret_cast(&static_cast(*this).value())) : nullptr; } [[nodiscard]] ALWAYS_INLINE T const* ptr() const& { return static_cast(*this).has_value() ? __builtin_launder(reinterpret_cast(&static_cast(*this).value())) : nullptr; } [[nodiscard]] ALWAYS_INLINE T value_or(T const& fallback) const& { if (static_cast(*this).has_value()) return static_cast(*this).value(); return fallback; } [[nodiscard]] ALWAYS_INLINE T value_or(T&& fallback) && { if (static_cast(*this).has_value()) return move(static_cast(*this).value()); return move(fallback); } template [[nodiscard]] ALWAYS_INLINE O value_or_lazy_evaluated(Callback callback) const { if (static_cast(*this).has_value()) return static_cast(*this).value(); return callback(); } template [[nodiscard]] ALWAYS_INLINE Optional value_or_lazy_evaluated_optional(Callback callback) const { if (static_cast(*this).has_value()) return static_cast(*this).value(); return callback(); } template [[nodiscard]] ALWAYS_INLINE ErrorOr try_value_or_lazy_evaluated(Callback callback) const { if (static_cast(*this).has_value()) return static_cast(*this).value(); return TRY(callback()); } template [[nodiscard]] ALWAYS_INLINE ErrorOr> try_value_or_lazy_evaluated_optional(Callback callback) const { if (static_cast(*this).has_value()) return static_cast(*this).value(); return TRY(callback()); } template ALWAYS_INLINE bool operator==(Optional const& other) const { return static_cast(*this).has_value() == (other).has_value() && (!static_cast(*this).has_value() || static_cast(*this).value() == (other).value()); } template requires(!Detail::IsBaseOf, O>) ALWAYS_INLINE bool operator==(O const& other) const { return static_cast(*this).has_value() && static_cast(*this).value() == other; } [[nodiscard]] ALWAYS_INLINE T const& operator*() const { return static_cast(*this).value(); } [[nodiscard]] ALWAYS_INLINE T& operator*() { return static_cast(*this).value(); } ALWAYS_INLINE T const* operator->() const { return &static_cast(*this).value(); } ALWAYS_INLINE T* operator->() { return &static_cast(*this).value(); } template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) { if constexpr (IsErrorOr) { if (static_cast(*this).has_value()) return OptionalType { TRY(mapper(static_cast(*this).value())) }; return OptionalType {}; } else { if (static_cast(*this).has_value()) return OptionalType { mapper(static_cast(*this).value()) }; return OptionalType {}; } } template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) const { if constexpr (IsErrorOr) { if (static_cast(*this).has_value()) return OptionalType { TRY(mapper(static_cast(*this).value())) }; return OptionalType {}; } else { if (static_cast(*this).has_value()) return OptionalType { mapper(static_cast(*this).value()) }; return OptionalType {}; } } }; template requires(!IsLvalueReference) class [[nodiscard]] Optional : public OptionalBase> { template friend class Optional; static_assert(!IsLvalueReference && !IsRvalueReference); public: using ValueType = T; ALWAYS_INLINE Optional() = default; template V> Optional(V) { } template V> Optional& operator=(V) { clear(); return *this; } AK_MAKE_CONDITIONALLY_COPYABLE(Optional, ); AK_MAKE_CONDITIONALLY_NONMOVABLE(Optional, ); AK_MAKE_CONDITIONALLY_DESTRUCTIBLE(Optional, ); ALWAYS_INLINE Optional(Optional const& other) requires(!IsTriviallyCopyConstructible) : m_has_value(other.m_has_value) { if (other.has_value()) new (&m_storage) T(other.value()); } ALWAYS_INLINE Optional(Optional&& other) : m_has_value(other.m_has_value) { if (other.has_value()) new (&m_storage) T(other.release_value()); } template requires(IsConstructible && !IsSpecializationOf && !IsSpecializationOf) ALWAYS_INLINE explicit Optional(Optional const& other) : m_has_value(other.has_value()) { if (other.has_value()) new (&m_storage) T(other.value()); } template requires(IsConstructible && !IsSpecializationOf && !IsSpecializationOf) ALWAYS_INLINE explicit Optional(Optional&& other) : m_has_value(other.has_value()) { if (other.has_value()) new (&m_storage) T(other.release_value()); } template requires(!IsSame>) ALWAYS_INLINE explicit(!IsConvertible) Optional(U&& value) requires(!IsSame, Optional> && IsConstructible) : m_has_value(true) { new (&m_storage) T(forward(value)); } ALWAYS_INLINE Optional& operator=(Optional const& other) requires(!IsTriviallyCopyConstructible || !IsTriviallyDestructible) { if (this != &other) { clear(); m_has_value = other.m_has_value; if (other.has_value()) { new (&m_storage) T(other.value()); } } return *this; } ALWAYS_INLINE Optional& operator=(Optional&& other) { if (this != &other) { clear(); m_has_value = other.m_has_value; if (other.has_value()) { new (&m_storage) T(other.release_value()); } } return *this; } template ALWAYS_INLINE bool operator==(Optional const& other) const { return has_value() == other.has_value() && (!has_value() || value() == other.value()); } template ALWAYS_INLINE bool operator==(O const& other) const { return has_value() && value() == other; } ALWAYS_INLINE ~Optional() requires(!IsTriviallyDestructible && IsDestructible) { clear(); } ALWAYS_INLINE void clear() { if (m_has_value) { value().~T(); m_has_value = false; } } template ALWAYS_INLINE void emplace(Parameters&&... parameters) { clear(); m_has_value = true; new (&m_storage) T(forward(parameters)...); } template ALWAYS_INLINE void lazy_emplace(Callable callable) { clear(); m_has_value = true; new (&m_storage) T { callable() }; } [[nodiscard]] ALWAYS_INLINE bool has_value() const { return m_has_value; } [[nodiscard]] ALWAYS_INLINE T& value() & { VERIFY(m_has_value); return *__builtin_launder(reinterpret_cast(&m_storage)); } [[nodiscard]] ALWAYS_INLINE T const& value() const& { VERIFY(m_has_value); return *__builtin_launder(reinterpret_cast(&m_storage)); } [[nodiscard]] ALWAYS_INLINE T value() && { return release_value(); } [[nodiscard]] ALWAYS_INLINE T release_value() { VERIFY(m_has_value); T released_value = move(value()); value().~T(); m_has_value = false; return released_value; } private: alignas(T) u8 m_storage[sizeof(T)]; bool m_has_value { false }; }; template requires(IsLvalueReference) class [[nodiscard]] Optional { template friend class Optional; template constexpr static bool CanBePlacedInOptional = IsSame, RemoveReference>> && (IsBaseOf, RemoveCVReference> || IsSame, RemoveCVReference>); public: using ValueType = T; ALWAYS_INLINE Optional() = default; template V> Optional(V) { } template V> Optional& operator=(V) { clear(); return *this; } template ALWAYS_INLINE Optional(U& value) requires(CanBePlacedInOptional) : m_pointer(&value) { } ALWAYS_INLINE Optional(RemoveReference& value) : m_pointer(&value) { } ALWAYS_INLINE Optional(Optional const& other) : m_pointer(other.m_pointer) { } ALWAYS_INLINE Optional(Optional&& other) : m_pointer(other.m_pointer) { other.m_pointer = nullptr; } template ALWAYS_INLINE Optional(Optional& other) requires(CanBePlacedInOptional) : m_pointer(other.ptr()) { } template ALWAYS_INLINE Optional(Optional const& other) requires(CanBePlacedInOptional) : m_pointer(other.ptr()) { } template ALWAYS_INLINE Optional(Optional&& other) requires(CanBePlacedInOptional) : m_pointer(other.ptr()) { other.m_pointer = nullptr; } ALWAYS_INLINE Optional& operator=(Optional& other) { m_pointer = other.m_pointer; return *this; } ALWAYS_INLINE Optional& operator=(Optional const& other) { m_pointer = other.m_pointer; return *this; } ALWAYS_INLINE Optional& operator=(Optional&& other) { m_pointer = other.m_pointer; other.m_pointer = nullptr; return *this; } template ALWAYS_INLINE Optional& operator=(Optional& other) requires(CanBePlacedInOptional) { m_pointer = other.ptr(); return *this; } template ALWAYS_INLINE Optional& operator=(Optional const& other) requires(CanBePlacedInOptional) { m_pointer = other.ptr(); return *this; } template ALWAYS_INLINE Optional& operator=(Optional&& other) requires(CanBePlacedInOptional && IsLvalueReference) { m_pointer = other.m_pointer; other.m_pointer = nullptr; return *this; } // Note: Disallows assignment from a temporary as this does not do any lifetime extension. template requires(!IsSame>) ALWAYS_INLINE Optional& operator=(U&& value) requires(CanBePlacedInOptional && IsLvalueReference) { m_pointer = &value; return *this; } ALWAYS_INLINE void clear() { m_pointer = nullptr; } [[nodiscard]] ALWAYS_INLINE bool has_value() const { return m_pointer != nullptr; } [[nodiscard]] ALWAYS_INLINE RemoveReference* ptr() { return m_pointer; } [[nodiscard]] ALWAYS_INLINE RemoveReference const* ptr() const { return m_pointer; } [[nodiscard]] ALWAYS_INLINE T value() { VERIFY(m_pointer); return *m_pointer; } [[nodiscard]] ALWAYS_INLINE AddConstToReferencedType value() const { VERIFY(m_pointer); return *m_pointer; } template requires(IsBaseOf, U>) [[nodiscard]] ALWAYS_INLINE AddConstToReferencedType value_or(U& fallback) const { if (m_pointer) return value(); return fallback; } // Note that this ends up copying the value. [[nodiscard]] ALWAYS_INLINE RemoveCVReference value_or(RemoveCVReference fallback) const { if (m_pointer) return value(); return fallback; } [[nodiscard]] ALWAYS_INLINE T release_value() { return *exchange(m_pointer, nullptr); } template ALWAYS_INLINE bool operator==(Optional const& other) const { return has_value() == other.has_value() && (!has_value() || value() == other.value()); } template ALWAYS_INLINE bool operator==(U const& other) const { return has_value() && value() == other; } ALWAYS_INLINE AddConstToReferencedType operator*() const { return value(); } ALWAYS_INLINE T operator*() { return value(); } ALWAYS_INLINE RawPtr>> operator->() const { return &value(); } ALWAYS_INLINE RawPtr> operator->() { return &value(); } // Conversion operators from Optional -> Optional ALWAYS_INLINE operator Optional>() const { if (has_value()) return Optional>(value()); return {}; } template [[nodiscard]] ALWAYS_INLINE T value_or_lazy_evaluated(Callback callback) const { if (m_pointer != nullptr) return value(); return callback(); } template [[nodiscard]] ALWAYS_INLINE Optional value_or_lazy_evaluated_optional(Callback callback) const { if (m_pointer != nullptr) return value(); return callback(); } template [[nodiscard]] ALWAYS_INLINE ErrorOr try_value_or_lazy_evaluated(Callback callback) const { if (m_pointer != nullptr) return value(); return TRY(callback()); } template [[nodiscard]] ALWAYS_INLINE ErrorOr> try_value_or_lazy_evaluated_optional(Callback callback) const { if (m_pointer != nullptr) return value(); return TRY(callback()); } template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) { if constexpr (IsErrorOr) { if (m_pointer != nullptr) return OptionalType { TRY(mapper(value())) }; return OptionalType {}; } else { if (m_pointer != nullptr) return OptionalType { mapper(value()) }; return OptionalType {}; } } template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) const { if constexpr (IsErrorOr) { if (m_pointer != nullptr) return OptionalType { TRY(mapper(value())) }; return OptionalType {}; } else { if (m_pointer != nullptr) return OptionalType { mapper(value()) }; return OptionalType {}; } } private: RemoveReference* m_pointer { nullptr }; }; } #if USING_AK_GLOBALLY using AK::Optional; using AK::OptionalNone; #endif