#pragma once #include #include #include #include template class Result; template class Option { public: Option(const T& value) : m_has_value(true) { m_storage.store_reference(value); } Option(T&& value) : m_has_value(true) { m_storage.store_moved_reference(move(value)); } Option(const Option& other) : m_has_value(other.m_has_value) { if (m_has_value) { m_storage.store_reference(other.m_storage.fetch_reference()); } } Option(Option&& other) : m_has_value(other.m_has_value) { other.m_has_value = false; if (m_has_value) { m_storage.store_moved_reference(move(other.m_storage.fetch_reference())); } } Option& operator=(const Option& other) { if (this == &other) return *this; if (m_has_value) m_storage.destroy(); m_has_value = other.m_has_value; if (m_has_value) { m_storage.store_reference(other.m_storage.fetch_reference()); } return *this; } Option& operator=(Option&& other) { if (this == &other) return *this; if (m_has_value) m_storage.destroy(); m_has_value = other.m_has_value; other.m_has_value = false; if (m_has_value) { m_storage.store_moved_reference(move(other.m_storage.fetch_reference())); } return *this; } Option() : m_has_value(false) { } bool has_value() const { return m_has_value; } T value(SourceLocation caller = SourceLocation::current()) const { expect_at(has_value(), caller, "Option::value called on an empty Option"); return m_storage.fetch_reference(); } T unchecked_value(Badge>) const { return m_storage.fetch_reference(); } T release_value(SourceLocation caller = SourceLocation::current()) { expect_at(has_value(), caller, "Option::release_value called on an empty Option"); m_has_value = false; return move(m_storage.fetch_reference()); } T unchecked_release_value(Badge>) { m_has_value = false; return move(m_storage.fetch_reference()); } T value_or(const T& other) const { if (has_value()) return m_storage.fetch_reference(); return other; } bool try_set_value(T& ref) const { if (!has_value()) return false; ref = m_storage.fetch_reference(); return true; } bool try_move_value(T& ref) { if (!has_value()) return false; m_has_value = false; ref = move(m_storage.fetch_reference()); return true; } ~Option() { if (has_value()) m_storage.destroy(); } // For compatibility with TRY() struct ErrorHandle { private: explicit ErrorHandle() { } friend class Option; }; Option(const ErrorHandle&) : m_has_value(false) { } ErrorHandle release_error(SourceLocation caller = SourceLocation::current()) { expect_at(!has_value(), caller, "Option::release_error called on a non-empty Option"); return ErrorHandle {}; } private: struct Storage { alignas(T) u8 buffer[sizeof(T)]; T& fetch_reference() { return *__builtin_launder(reinterpret_cast(&buffer)); } const T& fetch_reference() const { return *__builtin_launder(reinterpret_cast(&buffer)); } void store_reference(const T& ref) { new (buffer) T(ref); } void store_moved_reference(T&& ref) { new (buffer) T(move(ref)); } void destroy() { fetch_reference().~T(); } }; Storage m_storage; bool m_has_value { false }; }; template inline Option nonnull_or_empty_option(T* ptr) { if (ptr == nullptr) return {}; else return ptr; }