#pragma once #include #include #include "pointer.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 = 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 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 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); } } // --- }; 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(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; }