#pragma once #include "asl/assert.hpp" #include "asl/meta.hpp" #include "asl/maybe_uninit.hpp" #include "asl/functional.hpp" namespace asl { struct nullopt_t {}; static constexpr nullopt_t nullopt{}; // @Todo(option) Niche // @Todo(option) Reference // @Todo(option) Function // @Todo(option) Arrays template class option; namespace option_internal { template concept convertible_from_option = convertible_from&> && convertible_from&> && convertible_from&&> && convertible_from&&>; template concept constructible_from_option = constructible_from&> && constructible_from&> && constructible_from&&> && constructible_from&&>; template concept assignable_from_option = assignable_from&> && assignable_from&> && assignable_from&&> && assignable_from&&>; template concept convertible_constructible_from_option = convertible_from_option && constructible_from_option; template concept convertible_constructible_assignable_from_option = convertible_constructible_from_option && assignable_from_option; } // namespace option_internal template concept is_option = requires { typename T::type; requires same_as, option>; }; template class option { static constexpr bool kIsTrivial = trivially_default_constructible && trivially_copy_constructible && trivially_move_constructible && trivially_copy_assignable && trivially_move_assignable && trivially_destructible; using Storage = select_t>; Storage m_payload{}; bool m_has_value = false; template constexpr void construct(Args&&... args) & { ASL_ASSERT(!m_has_value); m_has_value = true; if constexpr (kIsTrivial) { new(&m_payload) T(ASL_FWD(args)...); } else { m_payload.init_unsafe(ASL_FWD(args)...); } } template constexpr void assign(Arg&& arg) & { ASL_ASSERT(m_has_value); if constexpr (kIsTrivial) { m_payload = ASL_FWD(arg); } else { m_payload.as_init_unsafe() = ASL_FWD(arg); } } public: using type = T; constexpr option() = default; constexpr option(nullopt_t) {} // NOLINT(*-explicit-conversions) template constexpr explicit (!convertible_from) option(U&& value) requires ( constructible_from && !same_as, option> ) { construct(ASL_FWD(value)); } constexpr option(const option& other) requires copy_constructible && kIsTrivial = default; constexpr option(const option& other) requires copy_constructible && (!kIsTrivial) { if (other.m_has_value) { construct(other.m_payload.as_init_unsafe()); } } constexpr option(const option& other) requires (!copy_constructible) = delete; constexpr option(option&& other) requires move_constructible && kIsTrivial = default; constexpr option(option&& other) requires move_constructible && (!kIsTrivial) { if (other.m_has_value) { construct(ASL_MOVE(other.m_payload.as_init_unsafe())); } } template constexpr explicit (!convertible_from) option(const option& other) requires ( constructible_from && !option_internal::convertible_constructible_from_option ) { if (other.has_value()) { construct(other.value()); } } template constexpr explicit (!convertible_from) option(option&& other) requires ( constructible_from && !option_internal::convertible_constructible_from_option ) { if (other.has_value()) { construct(ASL_MOVE(other).value()); } } constexpr option& operator=(nullopt_t) & { reset(); return *this; } template constexpr option& operator=(U&& value) & requires ( assignable_from && constructible_from && !same_as, option> ) { if (m_has_value) { assign(ASL_FWD(value)); } else { construct(ASL_FWD(value)); } return *this; } constexpr option& operator=(const option& other) & requires (!copy_assignable || copy_constructible) = delete; constexpr option& operator=(const option& other) & requires copy_assignable && copy_constructible && kIsTrivial = default; constexpr option& operator=(const option& other) & requires copy_assignable && copy_constructible && (!kIsTrivial) { if (&other == this) { return *this; } if (other.m_has_value) { if (m_has_value) { assign(other.m_payload.as_init_unsafe()); } else { construct(other.m_payload.as_init_unsafe()); } } else { reset(); } return *this; } constexpr option& operator=(option&& other) & requires move_assignable && move_constructible && kIsTrivial = default; constexpr option& operator=(option&& other) & requires move_assignable && move_constructible && (!kIsTrivial) { if (&other == this) { return *this; } if (other.m_has_value) { if (m_has_value) { assign(ASL_MOVE(other.m_payload.as_init_unsafe())); } else { construct(ASL_MOVE(other.m_payload.as_init_unsafe())); } } else { reset(); } return *this; } template constexpr option& operator=(const option& other) & requires ( constructible_from && assignable_from && !option_internal::convertible_constructible_assignable_from_option ) { if (other.has_value()) { if (m_has_value) { assign(other.value()); } else { construct(other.value()); } } else { reset(); } return *this; } template constexpr option& operator=(option&& other) & requires ( constructible_from && assignable_from && !option_internal::convertible_constructible_assignable_from_option ) { if (other.has_value()) { if (m_has_value) { assign(ASL_MOVE(other).value()); } else { construct(ASL_MOVE(other).value()); } } else { reset(); } return *this; } constexpr ~option() = default; constexpr ~option() requires (!trivially_destructible) { reset(); } constexpr void reset() { if constexpr (kIsTrivial) { m_has_value = false; } else if (m_has_value) { m_payload.uninit_unsafe(); m_has_value = false; } } constexpr bool has_value() const { return m_has_value; } constexpr T&& value() && { ASL_ASSERT_RELEASE(m_has_value); if constexpr (kIsTrivial) { return ASL_MOVE(m_payload); } else { return ASL_MOVE(m_payload).as_init_unsafe(); } } constexpr T& value() & { ASL_ASSERT_RELEASE(m_has_value); if constexpr (kIsTrivial) { return m_payload; } else { return m_payload.as_init_unsafe(); } } constexpr const T& value() const& { ASL_ASSERT_RELEASE(m_has_value); if constexpr (kIsTrivial) { return m_payload; } else { return m_payload.as_init_unsafe(); } } template constexpr T value_or(U&& other_value) const& requires copy_constructible && convertible_from { return has_value() ? value() : static_cast(ASL_FWD(other_value)); } template constexpr T value_or(U&& other_value) && requires move_constructible && convertible_from { return has_value() ? ASL_MOVE(value()) : static_cast(ASL_FWD(other_value)); } template constexpr T& emplace(Args&&... args) & requires constructible_from { if (m_has_value) { reset(); } construct(ASL_FWD(args)...); return value(); } template constexpr auto and_then(F&& f) & requires is_option> { if (has_value()) { return invoke(ASL_FWD(f), value()); } return un_cvref_t>{}; } template constexpr auto and_then(F&& f) const& requires is_option> { if (has_value()) { return invoke(ASL_FWD(f), value()); } return un_cvref_t>{}; } template constexpr auto and_then(F&& f) && requires is_option> { if (has_value()) { return invoke(ASL_FWD(f), ASL_MOVE(value())); } return un_cvref_t>{}; } template constexpr auto transform(F&& f) & { using U = un_cvref_t>; if (has_value()) { return option{ invoke(ASL_FWD(f), value()) }; } return option{}; } template constexpr auto transform(F&& f) const& { using U = un_cvref_t>; if (has_value()) { return option{ invoke(ASL_FWD(f), value()) }; } return option{}; } template constexpr auto transform(F&& f) && { using U = un_cvref_t>; if (has_value()) { return option{ invoke(ASL_FWD(f), ASL_MOVE(value())) }; } return option{}; } template constexpr option or_else(F&& f) const& requires same_as>, option> { return has_value() ? *this : invoke(ASL_FWD(f)); } template constexpr option or_else(F&& f) && requires same_as>, option> { return has_value() ? ASL_MOVE(*this) : invoke(ASL_FWD(f)); } }; template option(T) -> option; } // namespace asl