#pragma once #include #include #include #include "pointer.h" #include "exopt.h" /// A better std::optional namespace exopt::types { namespace optional [[gnu::visibility("internal")]] { template struct null_optimise; template struct null_optimise > { constexpr static inline bool value = true; using held_type = T*; using type = ptr::NonNull; constexpr static decltype(auto) convert_to_held(std::add_rvalue_reference_t t) noexcept { return t.get(); } constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t t) noexcept { return ptr::NonNull::new_unchecked(t); } constexpr static inline auto sentinel = nullptr; }; template struct null_optimise > { constexpr static inline bool value = true; using held_type = T*; using type = std::reference_wrapper; constexpr static decltype(auto) convert_to_held(std::add_rvalue_reference_t t) noexcept { return std::addressof(t.get()); } constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t t) noexcept { return std::ref(*t); } constexpr static inline auto sentinel = nullptr; }; template class null_optimise > { using boxed::Box; using box_t = Box; class /*XXX: Shouldn't be needed alignas(box_t)*/ invariant : box_t { //TODO: See phone notes. using box_t::UNSAFE; public: constexpr box_t& operator*() & noexcept { return *static_assert(this); } constexpr box_t const& operator*() const& noexcept { return *static_assert(this); } constexpr box_t&& operator*() && noexcept { return std::move(*static_assert(this)); } constexpr box_t const&& operator*() const&& noexcept { return std::move(*static_assert(this)); } constexpr static invariant&& back_conv(box_t&& b) noexcept { return static_cast(b); } constexpr static invariant const& back_conv(box_t const& b) noexcept { return static_cast(b); } constexpr static invariant& back_conv(box_t& b) noexcept { return static_cast(b); } constexpr static invariant const&& back_conv(box_t const&& b) noexcept { return static_cast(b); } constexpr invariant(box_t&& m) noexcept : box_t(UNSAFE, std::move(std::move(m).as_unique(UNSAFE))) {} constexpr invariant(box_t const& m) noexcept : box_t(UNSAFE, m) {} constexpr invariant(std::unique_ptr&& m) noexcept : box_t(UNSAFE, std::move(m)) {} constexpr invariant(std::nullptr_t) noexcept : box_t(UNSAFE, std::unique_ptr { nullptr } ) {} constexpr friend auto operator<=>(invariant const& a, invariant const& b) noexcept { return (*a).as_unsafe_ptr(UNSAFE) <=> (*b).as_unsafe_ptr(UNSAFE); } constexpr friend auto operator<=>(invariant const& a, box_t const& b) noexcept { return (*a).as_unsafe_ptr(UNSAFE) <=> b.as_unsafe_ptr(UNSAFE); } constexpr friend auto operator<=>(invariant const& a, T const* p) noexcept { return (*a).as_unsafe_ptr(UNSAFE) <=> p; } constexpr friend auto operator<=>(invariant const& a, std::nullptr_t) noexcept { return (*a).as_unsafe_ptr(UNSAFE) <=> nullptr; } constexpr invariant& operator=(box_t &&) noexcept = delete; constexpr invariant& operator=(box_t const&) = delete; constexpr invariant& operator=(invariant &&) noexcept = default; constexpr invariant& operator=(invariant const&) = default; constexpr invariant& operator=(std::nullptr_t) { box_t::as_unique(UNSAFE).reset(); return *this; } constexpr invariant& operator=(T* const&& p) { box_t::as_unique(UNSAFE).reset(p); return *this; } constexpr invariant(invariant&& m) noexcept : invariant( std::move((*std::move(m)).as_unique(UNSAFE)) ) {} constexpr invariant(invariant const& m) noexcept : box_t(UNSAFE, (*m).as_unique(UNSAFE)) {} constexpr ~invariant() = default; }; static_assert(util::shares_layout, "invariant (held_type) does not share layout with viewed type (Box)"); public: typedef invariant held_type; // invariant ^: Held internal type. using type = box_t; // Box: API seen & operated on type constexpr static decltype(auto) convert_to_held(type ty) noexcept { return held_type{std::move(ty)}; } constexpr static decltype(auto) convert_to_held(type& ty) noexcept { return held_type::back_conv(ty); } constexpr static decltype(auto) convert_to_held(type&& ty) noexcept { return held_type::back_conv(std::move(ty)); } constexpr static decltype(auto) convert_to_held(type const& ty) noexcept { return held_type::back_conv(ty); } constexpr static decltype(auto) convert_to_held(type const&& ty) noexcept { return held_type::back_conv(std::move(ty)); } constexpr static type& convert_from_held(held_type& ty) noexcept { return *ty; } constexpr static type const&& convert_from_held(held_type const&& ty) noexcept { return std::move(*std::move(ty)); } constexpr static type&& convert_from_held(held_type&& ty) noexcept { return std::move(*std::move(ty)); } constexpr static type const& convert_from_held(held_type const& ty) noexcept { return *ty; } //TODO: See phone notes. constexpr static inline auto sentinel = nullptr; }; #ifdef DEBUG /* We don't want to instantiate `null_optimise<>` *at all* if we don't have to outside of actual real use in `Option`. This check is for development only since Option> is a bit of an awkward developmental case. Save compile time for release builds, the `static_assert` inside the null-opt partial spec for `..::invariant`'s layout will be checked if/when `Option>` is ever instantiated. This check is the same just assuring it's valid for *if* Option> is ever instantiated */ static_assert(!requires(requires{ typename boxed::Box; }) or // Skip check if `boxed.h` is not included. requires(requires(null_optimise>::held_type const& held) { { static_cast const&>(held) }; }), "Nullopt held_type for Box either has a unneeded and unwanted generated vtable or has invalid alignment, check into that"); #endif template struct null_optimise > { constexpr static inline bool value = true; using held_type = T __restrict__*; using type = ptr::Unique; constexpr static decltype(auto) convert_to_held(std::add_rvalue_reference_t t) noexcept { return t.get(); } constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t t) noexcept { return ptr::Unique::new_unchecked(t); } constexpr static inline auto sentinel = nullptr; }; template struct null_optimise { constexpr static inline bool value = true; using held_type = T*; using type = T&; constexpr static decltype(auto) convert_to_held(type t) noexcept { return std::addressof(t); } constexpr static auto& convert_from_held(held_type h) noexcept { return static_cast(*h); } constexpr static inline auto sentinel = nullptr; }; /* XXX: We can't optimise for this case with this model... template requires(sizeof(T&&) <= sizeof(T*)) struct null_optimise { constexpr static inline bool value = true; using held_type = T; using type = T&&; constexpr static decltype(auto) convert_to_held(type t) noexcept(std::is_nothrow_move_constructible_v) { return std::move(t); } constexpr static auto&& convert_from_held(held_type h) noexcept { return std::forward(h); } constexpr static inline auto sentinel = nullptr; };*/ template struct null_optimise { constexpr static inline bool value = true;//sizeof(T&&) == sizeof(T*); using held_type = T*; using type = T&&; constexpr static decltype(auto) convert_to_held(type t) noexcept { return std::addressof(t); } constexpr static auto&& convert_from_held(held_type h) noexcept { return std::forward(*h); } constexpr static inline auto sentinel = nullptr; }; template<> struct null_optimise { constexpr static inline bool value = true; using held_type = void; using type = void; }; template concept has_null_opt = std::is_void_v || requires { typename null_optimise::type; typename null_optimise::held_type; requires(null_optimise::value); /*requires(std::is_void_v::held_type> || requires(std::add_rvalue_reference_t< null_optimise::held_type> held) { { null_optimise::sentinel == std::as_const(held) } -> std::convertible_to; });*/ /*requires(null_optimise::type unheld , std::add_rvalue_reference_t::held_type> held) { //, const std::remove_reference_t::held_type>& held_const) { { null_optimise::sentinel } -> std::convertible_to::held_type>; { null_optimise::convert_to_held(unheld) } -> std::convertible_to::held_type>; { null_optimise::convert_from_held(held) } -> std::convertible_to::type>; { null_optimise::sentinel == std::as_const(held) } -> std::convertible_to; //{ null_optimise::convert_from_held(held_const) } -> std::convertible_to::type>; }; requires(std::is_same_v::type>);*/ }; namespace _null_opt_impl [[gnu::visibility("hidden")]] { template struct types_for; template struct types_for { static_assert(has_null_opt, "Type does not have nullptr optimisation enabled"); using held = null_optimise::held_type; using type = null_optimise::type; }; template struct types_for { using held=T; using type=T; }; template using held_type = typename types_for>::held; template using used_type = typename types_for>::type; } namespace _details [[gnu::visibility("hidden")]] { template struct not_void { using type = T; }; template<> struct not_void { struct type; }; template using not_void_t = typename not_void::type; template struct rv_ref_or_void { using type = std::add_rvalue_reference_t; }; template<> struct rv_ref_or_void { struct type; }; template using rv_ref_t = typename rv_ref_or_void::type; template struct cv_ref_or_void { using type = const std::remove_reference_t&; }; template<> struct cv_ref_or_void { struct _type; using type = const _type&; }; template using cv_ref_t = typename cv_ref_or_void::type; template struct const_ref_or_void { using type = const std::remove_const_t&; }; template<> struct const_ref_or_void { struct type; }; template using const_ref_t = typename const_ref_or_void::type; template struct if_then_else_type; template struct if_then_else_type { using type = If; using other = U; constexpr static inline bool result = true; }; template struct if_then_else_type { using type = Else; using other = T; constexpr static inline bool result = false; }; template using if_then_else_t = typename if_then_else_type::type; template class any_ref_or_void { template> struct is_rv_ref_type; template struct is_rv_ref_type { using type = U; static_assert(std::is_rvalue_reference_v, "Invalid reference"); }; template struct is_rv_ref_type { using type = U&; static_assert(!std::is_rvalue_reference_v, "Invalid reference"); }; public: using type = is_rv_ref_type>::type; }; /* XXX: Having to account for all qualifiers is fucking dreck... we'd also need all variations of this with `__restrict__` too.. template struct any_ref_or_void { using type = T&&; }; template struct any_ref_or_void { using type = const T&&; }; template struct any_ref_or_void { using type = volatile T&&; }; template struct any_ref_or_void { using type = const volatile T&&; }; */ template<> struct any_ref_or_void { struct type; }; template using any_ref_t = typename any_ref_or_void::type; template struct mut_ref_or_void { using type = any_ref_or_void>::type; }; template<> struct mut_ref_or_void { struct type; }; template using mut_ref_t = typename mut_ref_or_void::type; template requires(requires(T v) { { v == nullptr } -> std::convertible_to; }) [[gnu::always_inline]] constexpr decltype(auto) map_null(T&& value, auto&& with) noexcept(std::is_nothrow_invocable_v(value))>) { return (!bool(value == nullptr)) ? with(std::forward(value)) : nullptr; } template requires(requires(T v) { { v == nullptr } -> std::convertible_to; }) [[gnu::always_inline]] constexpr decltype(auto) map_null(T&& value, auto&& with, std::add_rvalue_reference_t otherwise) noexcept(std::is_nothrow_invocable_v(value))> && (!std::is_invocable_v || std::is_nothrow_invocable_v)) { if (!bool(value == nullptr)) return with(std::forward(value)); else if constexpr(std::is_invocable_v) return otherwise(); else return std::forward(otherwise); } template constexpr inline bool is_same_any_v = (std::is_same_v || ...); constexpr inline auto identity = [] (T v) noexcept { return std::forward(v); }; } struct none_t { constexpr none_t() noexcept = default; constexpr ~none_t() noexcept = default; constexpr none_t(std::nullptr_t) noexcept : none_t() {} constexpr none_t(std::nullopt_t) noexcept : none_t() {} constexpr operator std::nullptr_t() const noexcept { return nullptr; } }; constexpr inline none_t None; template concept AsOption = (std::is_void_v || requires{ requires(sizeof(T) == sizeof(T)); } ) && !_details::is_same_any_v; struct OptionUnwrapError /*TODO : public virtual error::Error, virtual std::exception*/ { constexpr static inline const auto MESSAGE = "Unwrap operation on a None value"; OptionUnwrapError() noexcept = default; inline virtual ~OptionUnwrapError() noexcept {} inline std::string_view message() const noexcept /*override*/ { return std::string_view{MESSAGE}; } }; template struct Option final { struct UnwrapError : public OptionUnwrapError { UnwrapError() noexcept =default; inline UnwrapError(const UnwrapError&) noexcept : UnwrapError() {} inline UnwrapError(UnwrapError&&) noexcept : UnwrapError() {} inline virtual ~UnwrapError() noexcept {} using OptionUnwrapError::message; }; constexpr static inline bool is_null_optimised = has_null_opt; constexpr friend void swap(Option& a, Option& b) noexcept { using std::swap; swap(a.inner_, b.inner_); //XXX: Swap the values if possible? } constexpr Option(std::optional&& opt) noexcept requires(!std::is_void_v) : inner_( bool(opt) ? std::move(*opt) : nullptr ) {} constexpr explicit Option(std::nullptr_t) noexcept : inner_(nullptr) {} constexpr Option(none_t) noexcept : Option(nullptr) {} constexpr ~Option() noexcept(std::is_nothrow_destructible_v) {} constexpr Option(const Option& copy) noexcept(std::is_nothrow_copy_constructible_v) : inner_(copy.inner_) {} constexpr Option(Option&& move) noexcept(std::is_nothrow_move_constructible_v) : inner_(std::move(move.inner_)) {} constexpr explicit(std::convertible_to, Option>) Option(_details::rv_ref_t value) noexcept(std::is_nothrow_invocable_v>) requires(!std::is_void_v) : inner_(convert_to_held_rv(std::forward(value))) {} //TODO: copy/move assign where appropriate constexpr Option& operator=(const Option& copy) noexcept(std::is_void_v || std::is_nothrow_copy_constructible_v) { if constexpr(std::is_void_v) { inner_.has = copy.inner_.has; } else { if(this != std::addressof(copy)) { using inner_t = std::remove_reference_t; if(const auto* ptr = copy.try_get_value()) { using held_t = inner_t::held_type; inner_ = inner_t{held_t{*ptr}}; } else inner_ = inner_t{None}; } } return *this; } constexpr Option& operator=(Option&& move) noexcept(std::is_void_v || std::is_nothrow_move_constructible_v) { if constexpr(std::is_void_v) std::exchange(inner_.has, move.inner_.has); else { if(this != std::addressof(move)) { using inner_t = std::remove_reference_t; if(auto* ptr = move.try_get_value()) { inner_ = inner_t{ std::move(*ptr) }; } else inner_ = inner_t{None}; } } return *this; } constexpr Option& operator=(none_t) noexcept(std::is_nothrow_destructible_v>().inner_)::held_type>) { using inner_t = std::remove_reference_t; inner_ = inner_t{ None }; return *this; } //TODO: XXX: For this to work, we need *another* partial-spec "trait" that is (T == void ? void || alias std::add_rvalue_reference_t) constexpr explicit(std::convertible_to, Option>) Option& operator=(_details::rv_ref_t value) noexcept(std::is_void_v || std::is_nothrow_constructible_v>().inner_), std::add_rvalue_reference_t>>>) requires(!std::is_void_v) { using inner_t = std::remove_reference_t; if constexpr (!std::is_void_v) inner_ = inner_t { convert_to_held_rv(std::forward(value)) }; else inner_ = inner_t { true }; return *this; } //accessors constexpr bool has_value() const noexcept { return inner_.has_value(); } //XXX: We're returning held_type* here, which we don't want to expose... constexpr const auto* try_get_value() const noexcept { return _details::map_null(inner_.try_get_value(), [](const auto* ptr) { return std::addressof(convert_from_held_cv(*ptr)); }); } constexpr auto* try_get_value() noexcept { return _details::map_null(inner_.try_get_value(), [](auto* ptr) { return std::addressof(convert_from_held_ref(*ptr)); }); } //TODO: XXX: We need to `convert_from_held` here! constexpr const auto& value() const& { if (const auto* p = try_get_value()) return convert_from_held_cv(*p); throw _unwrap_error(); } constexpr auto& value() & { if(auto* p = try_get_value()) return convert_from_held_ref(*p); throw _unwrap_error(); } constexpr decltype(auto) value() && { if(auto* p = try_get_value()) return convert_from_held_rv(std::move(*p)); throw _unwrap_error(); } constexpr auto value() const&& { if(const auto* p = try_get_value()) return auto(convert_from_held_cv(*p)); throw _unwrap_error(); } constexpr operator bool() const noexcept { return has_value(); } constexpr const auto* operator->() const { return _details::map_null(try_get_value(), _details::identity, [] [[noreturn]] () -> std::nullptr_t { throw _unwrap_error(); }); } constexpr auto* operator->() { return _details::map_null(try_get_value(), _details::identity, [] [[noreturn]] () -> std::nullptr_t { throw _unwrap_error(); }); } constexpr const auto& operator*() const& { return value(); } constexpr auto& operator*() & { return value(); } constexpr decltype(auto) operator*() && { return value(); } constexpr auto operator*() const&& { return value(); } private: constexpr static decltype(auto) _unwrap_error() { if consteval { return UnwrapError{}; } else { return std::runtime_error(UnwrapError{}.message()); } } struct _impl_base { typedef _null_opt_impl::held_type held_type; typedef _null_opt_impl::used_type type; //struct none_type {}; typedef none_t none_type; constexpr _impl_base() noexcept = default; constexpr ~_impl_base() noexcept = default; }; template struct _impl; template struct _impl_type { using type = _impl<_U, O>; }; template struct _impl_type { static_assert(O, "void should be `null_opt`"); /// std::is_void_v struct alignas(bool) type final : public _impl_base { using held_type = _impl_base::held_type; using none_type = _impl_base::none_type; constexpr type(std::nullptr_t) noexcept : _impl_base(), has(false) {} constexpr explicit type(bool&& value) noexcept : _impl_base(), has(value) {} constexpr ~type() noexcept {} constexpr bool has_value() const noexcept { return has; } constexpr const bool* try_get_value() const noexcept { return &has; } constexpr bool* try_get_value() noexcept { return &has; } constexpr bool get_value() const noexcept { return has; } constexpr bool& get_value() noexcept { return has; } bool has; }; }; /// null_opt == true template struct alignas(_U) _impl<_U, true> final : public _impl_base { using type_info = null_optimise; using held_type = _impl_base::held_type; using none_type = _impl_base::none_type; static_assert(has_null_opt, "Invalid use of Option::_impl where has_null_opt == false"); constexpr _impl(std::nullptr_t) noexcept : _impl_base(), value(type_info::sentinel) {} constexpr _impl(std::add_rvalue_reference_t value) noexcept(std::is_nothrow_move_constructible_v) : _impl_base() , value(std::move(value)) {} /*//TODO: XXX: Make Option(T&&) ctor handle calling `convert_to_held` instead? Another template interface for this?? constexpr explicit _impl(std::add_rvalue_reference_t value) noexcept(std::is_nothrow_constructible_v> && std::is_nothrow_invocable_v) : _impl_base() , value(type_info::convert_to_held(std::forward(value))) {}*/ constexpr _impl& operator=(std::add_rvalue_reference_t value) noexcept(std::is_nothrow_move_constructible_v) { std::exchange(value, std::forward(value)); return *this; } constexpr _impl& operator=(none_t) noexcept(std::is_nothrow_destructible_v) { if(has_value()) { using sen_t = decltype(type_info::sentinel); std::exchange(value, sen_t{type_info::sentinel}); } return *this; } constexpr ~_impl() noexcept(std::is_nothrow_destructible_v) {} constexpr bool has_value() const noexcept { return !bool(type_info::sentinel == value); } constexpr const held_type* try_get_value() const noexcept { return has_value() ? std::addressof(value) : nullptr; } constexpr held_type* try_get_value() noexcept { return has_value() ? std::addressof(value) : nullptr; } held_type value; }; /// null_opt == false template struct alignas(_U) _impl<_U, false> final : public _impl_base { using held_type = _impl_base::held_type; using none_type = _impl_base::none_type; constexpr _impl(std::nullptr_t) noexcept : _impl_base(), has(false), none(none_type{}) {} constexpr _impl(std::add_rvalue_reference_t value) noexcept(std::is_nothrow_move_constructible_v) : _impl_base() , has(true) , some(std::move(value)) {} constexpr _impl& operator=(std::add_rvalue_reference_t value) noexcept(std::is_nothrow_move_constructible_v && std::is_nothrow_destructible_v) { if(has) std::exchange(some, std::forward(value)); else { none.~none_type(); if constexpr(noexcept(std::is_nothrow_move_constructible_v)) some = std::move(value); else try { some = std::move(value); } catch(...) { none = none_type{}; throw; } has = true; } return *this; } constexpr _impl& operator=(none_t) noexcept(std::is_nothrow_destructible_v) { if(has) { some.~held_type(); none = none_type{}; has = false; } return *this; } constexpr ~_impl() noexcept(std::is_nothrow_destructible_v) { if(has) some.~held_type(); else none.~none_type(); } constexpr bool has_value() const noexcept { return has; } constexpr const held_type* try_get_value() const noexcept { return has ? std::addressof(some) : nullptr; } constexpr held_type* try_get_value() noexcept { return has ? std::addressof(some) : nullptr; } bool has; union { held_type some; [[no_unique_address]] _impl_base::none_type none; }; }; _impl_type::type inner_; // --- `held_type` convertions constexpr static decltype(auto) convert_to_held_rv(_details::rv_ref_t from) noexcept(!is_null_optimised || std::is_nothrow_invocable_v::convert_to_held, _details::rv_ref_t>) { if constexpr(is_null_optimised) { return null_optimise::convert_to_held(std::forward(from)); } else { return std::forward(from); } } constexpr static decltype(auto) convert_to_held_cv(_details::cv_ref_t from) noexcept(!is_null_optimised || std::is_nothrow_invocable_v::convert_to_held, _details::cv_ref_t>) { if constexpr(is_null_optimised) { return null_optimise::convert_to_held(from); } else { return std::forward(from); } } using this_held_type = typename std::remove_reference_t< decltype(std::declval>() .inner_)> ::held_type; constexpr static decltype(auto) convert_from_held_cv( _details::cv_ref_t held) noexcept(!is_null_optimised || std::is_nothrow_invocable_v::convert_from_held, _details::cv_ref_t>) { if constexpr(is_null_optimised) { return null_optimise::convert_from_held(std::forward(held)); } else { return std::forward(held); } } constexpr static decltype(auto) convert_from_held_ref( _details::mut_ref_t held) noexcept(!is_null_optimised || std::is_nothrow_invocable_v::convert_from_held, _details::mut_ref_t>) { if constexpr(is_null_optimised) { return null_optimise::convert_from_held(std::forward(held)); } else { return std::forward(held); } } constexpr static decltype(auto) convert_from_held_rv( _details::rv_ref_t held) noexcept(!is_null_optimised || std::is_nothrow_invocable_v::convert_from_held, _details::rv_ref_t>) { if constexpr(is_null_optimised) { return null_optimise::convert_from_held(std::forward(held)); } else { return std::forward(held); } } // --- }; template requires(requires(T&& p) { static_cast(p); }) constexpr Option static_opt_cast(Option&& p) noexcept { if(bool(p)) return Option { static_cast(std::move(*p)) }; return Option { nullptr }; } template requires(requires(T&& p) { static_cast(p); }) constexpr Option dynamic_opt_cast(Option&& p) { if(T* ptr = p.try_get_value()) if(U* pv = dynamic_cast(ptr)) return Option { std::move(*pv) }; return Option { nullptr }; } static_assert(sizeof(Option) > sizeof(void*), "Option: Bad null_opt size"); static_assert(alignof(Option) == alignof(void*), "Option: Bad null_opt align"); static_assert(sizeof(Option>) == sizeof(int*), "Option>: Bad null_opt size"); static_assert(alignof(Option>) == alignof(int*), "Option>: Bad null_opt align"); static_assert(sizeof(Option>) == sizeof(int*), "Option>: Bad null_opt size"); static_assert(alignof(Option>) == alignof(int*), "Option>: Bad null_opt align"); static_assert(sizeof(Option) == sizeof(double&), "Option: Bad null_opt size"); static_assert(alignof(Option) == alignof(double*), "Option: Bad null_opt align"); static_assert(sizeof(Option) == sizeof(char*), "Option: Bad null_opt size"); static_assert(alignof(Option) == alignof(long*), "Option: Bad null_opt align"); static_assert(alignof(Option) == alignof(int), "Option: Bad non null_opt align"); static_assert(sizeof(Option) == sizeof(bool), "Option: Bad size"); static_assert(alignof(Option) == alignof(bool), "Option: Bad align"); } using optional::Option; }