/* * Copyright (c) 2018-2021, Andreas Kling * Copyright (c) 2021, Daniel Bertalan * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #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]] Optional { 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; } #ifdef AK_HAS_CONDITIONALLY_TRIVIAL Optional(Optional const& other) requires(!IsCopyConstructible) = delete; Optional(Optional const& other) = default; Optional(Optional&& other) requires(!IsMoveConstructible) = delete; Optional& operator=(Optional const&) requires(!IsCopyConstructible || !IsDestructible) = delete; Optional& operator=(Optional const&) = default; Optional& operator=(Optional&& other) requires(!IsMoveConstructible || !IsDestructible) = delete; ~Optional() requires(!IsDestructible) = delete; ~Optional() = default; #endif ALWAYS_INLINE Optional(Optional const& other) #ifdef AK_HAS_CONDITIONALLY_TRIVIAL requires(!IsTriviallyCopyConstructible) #endif : 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.m_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.m_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) #ifdef AK_HAS_CONDITIONALLY_TRIVIAL requires(!IsTriviallyCopyConstructible || !IsTriviallyDestructible) #endif { 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() #ifdef AK_HAS_CONDITIONALLY_TRIVIAL requires(!IsTriviallyDestructible) #endif { 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; } [[nodiscard]] ALWAYS_INLINE T value_or(T const& fallback) const& { if (m_has_value) return value(); return fallback; } [[nodiscard]] ALWAYS_INLINE T value_or(T&& fallback) && { if (m_has_value) return move(value()); return move(fallback); } template [[nodiscard]] ALWAYS_INLINE T value_or_lazy_evaluated(Callback callback) const { if (m_has_value) return value(); return callback(); } template [[nodiscard]] ALWAYS_INLINE Optional value_or_lazy_evaluated_optional(Callback callback) const { if (m_has_value) return value(); return callback(); } template [[nodiscard]] ALWAYS_INLINE ErrorOr try_value_or_lazy_evaluated(Callback callback) const { if (m_has_value) return value(); return TRY(callback()); } template [[nodiscard]] ALWAYS_INLINE ErrorOr> try_value_or_lazy_evaluated_optional(Callback callback) const { if (m_has_value) return value(); return TRY(callback()); } ALWAYS_INLINE T const& operator*() const { return value(); } ALWAYS_INLINE T& operator*() { return value(); } ALWAYS_INLINE T const* operator->() const { return &value(); } ALWAYS_INLINE T* operator->() { return &value(); } template()(declval())), auto IsErrorOr = IsSpecializationOf, typename OptionalType = Optional>> ALWAYS_INLINE Conditional, OptionalType> map(F&& mapper) { if constexpr (IsErrorOr) { if (m_has_value) return OptionalType { TRY(mapper(value())) }; return OptionalType {}; } else { if (m_has_value) 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_has_value) return OptionalType { TRY(mapper(value())) }; return OptionalType {}; } else { if (m_has_value) return OptionalType { mapper(value()) }; return OptionalType {}; } } static FlatPtr value_offset() { return OFFSET_OF(Optional, m_storage); } static FlatPtr has_value_offset() { return OFFSET_OF(Optional, m_has_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 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 const& other) requires(CanBePlacedInOptional) : m_pointer(other.m_pointer) { } template ALWAYS_INLINE Optional(Optional&& other) requires(CanBePlacedInOptional) : m_pointer(other.m_pointer) { other.m_pointer = nullptr; } 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 const& other) requires(CanBePlacedInOptional) { m_pointer = other.m_pointer; return *this; } template ALWAYS_INLINE Optional& operator=(Optional&& other) requires(CanBePlacedInOptional) { 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 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 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