You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libexopt/include/optional.h

647 lines
26 KiB

#pragma once
#include <optional>
#include <utility>
#include <concepts>
#include "pointer.h"
/// A better std::optional
namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
template<typename T>
struct null_optimise;
template<typename T>
struct null_optimise<ptr::NonNull<T> > { constexpr static inline bool value = true;
using held_type = T*;
using type = ptr::NonNull<T>;
constexpr static decltype(auto) convert_to_held(std::add_rvalue_reference_t<type> t) noexcept { return t.get(); }
constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t<held_type> t) noexcept { return ptr::NonNull<T>::new_unchecked(t); }
constexpr static inline auto sentinel = nullptr;
};
template<typename T>
struct null_optimise<std::reference_wrapper<T> > { constexpr static inline bool value = true;
using held_type = T*;
using type = std::reference_wrapper<T>;
constexpr static decltype(auto) convert_to_held(std::add_rvalue_reference_t<type> t) noexcept { return std::addressof(t.get()); }
constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t<held_type> t) noexcept { return std::ref<T>(*t); }
constexpr static inline auto sentinel = nullptr;
};
template<typename T>
struct null_optimise<ptr::Unique<T> > { constexpr static inline bool value = true;
using held_type = T __restrict__*;
using type = ptr::Unique<T>;
constexpr static decltype(auto) convert_to_held(std::add_rvalue_reference_t<type> t) noexcept { return t.get(); }
constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t<held_type> t) noexcept { return ptr::Unique<T>::new_unchecked(t); }
constexpr static inline auto sentinel = nullptr;
};
template<typename T>
struct null_optimise<T&> { 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<type>(*h); }
constexpr static inline auto sentinel = nullptr;
};
/* XXX: We can't optimise for this case with this model...
template<typename T> requires(sizeof(T&&) <= sizeof(T*))
struct null_optimise<T&&> { 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<T>) { return std::move(t); }
constexpr static auto&& convert_from_held(held_type h) noexcept { return std::forward<type>(h); }
constexpr static inline auto sentinel = nullptr;
};*/
template<typename T>
struct null_optimise<T&&> { 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<type>(*h); }
constexpr static inline auto sentinel = nullptr;
};
template<>
struct null_optimise<void> { constexpr static inline bool value = true;
using held_type = void;
using type = void;
};
template<typename T>
concept has_null_opt = std::is_void_v<T>
|| requires {
typename null_optimise<T>::type;
typename null_optimise<T>::held_type;
requires(null_optimise<T>::value);
/*requires(std::is_void_v<null_optimise<T>::held_type> || requires(std::add_rvalue_reference_t< null_optimise<T>::held_type> held) {
{ null_optimise<T>::sentinel == std::as_const(held) } -> std::convertible_to<bool>;
});*/
/*requires(null_optimise<T>::type unheld
, std::add_rvalue_reference_t<null_optimise<T>::held_type> held) {
//, const std::remove_reference_t<null_optimise<T>::held_type>& held_const) {
{ null_optimise<T>::sentinel } -> std::convertible_to<null_optmimise<T>::held_type>;
{ null_optimise<T>::convert_to_held(unheld) } -> std::convertible_to<null_optimise<T>::held_type>;
{ null_optimise<T>::convert_from_held(held) } -> std::convertible_to<null_optimise<T>::type>;
{ null_optimise<T>::sentinel == std::as_const(held) } -> std::convertible_to<bool>;
//{ null_optimise<T>::convert_from_held(held_const) } -> std::convertible_to<null_optimise<T>::type>;
};
requires(std::is_same_v<T, null_optimise<T>::type>);*/
};
namespace _null_opt_impl [[gnu::visibility("hidden")]] {
template<typename, bool> struct types_for;
template<typename T>
struct types_for<T, true> {
static_assert(has_null_opt<T>, "Type does not have nullptr optimisation enabled");
using held = null_optimise<T>::held_type;
using type = null_optimise<T>::type;
};
template<typename T>
struct types_for<T, false> {
using held=T;
using type=T;
};
template<typename T>
using held_type = typename types_for<T, has_null_opt<T>>::held;
template<typename T>
using used_type = typename types_for<T, has_null_opt<T>>::type;
}
namespace _details [[gnu::visibility("hidden")]] {
template<typename T>
struct not_void { using type = T; };
template<>
struct not_void<void> { struct type; };
template<typename T>
using not_void_t = typename not_void<T>::type;
template<typename T>
struct rv_ref_or_void { using type = std::add_rvalue_reference_t<T>; };
template<>
struct rv_ref_or_void<void> { struct type; };
template<typename T>
using rv_ref_t = typename rv_ref_or_void<T>::type;
template<typename T>
struct cv_ref_or_void { using type = const std::remove_reference_t<T>&; };
template<>
struct cv_ref_or_void<void> { struct _type; using type = const _type&; };
template<typename T>
using cv_ref_t = typename cv_ref_or_void<T>::type;
template<typename T>
struct const_ref_or_void { using type = const std::remove_const_t<T>&; };
template<>
struct const_ref_or_void<void> { struct type; };
template<typename T>
using const_ref_t = typename const_ref_or_void<T>::type;
template<typename T, typename U, bool>
struct if_then_else_type;
template<typename If, typename U>
struct if_then_else_type<If, U, true> { using type = If; using other = U; constexpr static inline bool result = true; };
template<typename T, typename Else>
struct if_then_else_type<T, Else, false> { using type = Else; using other = T; constexpr static inline bool result = false; };
template<bool Predicate, typename Then, typename Else>
using if_then_else_t = typename if_then_else_type<Then, Else, Predicate>::type;
template<typename T>
class any_ref_or_void {
template<typename _U = T, bool P = std::is_rvalue_reference_v<T>>
struct is_rv_ref_type;
template<typename U>
struct is_rv_ref_type<U, true> { using type = U; static_assert(std::is_rvalue_reference_v<type>, "Invalid reference"); };
template<typename U>
struct is_rv_ref_type<U, false> { using type = U&; static_assert(!std::is_rvalue_reference_v<U>, "Invalid reference"); };
public:
using type = is_rv_ref_type<T, std::is_rvalue_reference_v<T>>::type;
};
/* XXX: Having to account for all qualifiers is fucking dreck... we'd also need all variations of this with `__restrict__` too..
template<typename T>
struct any_ref_or_void<T&&> { using type = T&&; };
template<typename T>
struct any_ref_or_void<const T&&> { using type = const T&&; };
template<typename T>
struct any_ref_or_void<volatile T&&> { using type = volatile T&&; };
template<typename T>
struct any_ref_or_void<const volatile T&&> { using type = const volatile T&&; };
*/
template<>
struct any_ref_or_void<void> { struct type; };
template<typename T>
using any_ref_t = typename any_ref_or_void<T>::type;
template<typename T>
struct mut_ref_or_void { using type = any_ref_or_void<std::remove_const_t<T>>::type; };
template<>
struct mut_ref_or_void<void> { struct type; };
template<typename T>
using mut_ref_t = typename mut_ref_or_void<T>::type;
template<typename T>
using remove_ref_keep_const_t = std::conditional_t,
<std::is_const_v<std::remove_reference_v<T> >
,const std::remove_cvref_t<T>
, std::remove_cvref_t<T> >;
template<typename T> requires(requires(T v) { { v == nullptr } -> std::convertible_to<bool>; })
[[gnu::always_inline]]
constexpr decltype(auto) map_null(T&& value, auto&& with) noexcept(std::is_nothrow_invocable_v<decltype(with), decltype(std::forward<T>(value))>) { return (!bool(value == nullptr)) ? with(std::forward<T>(value)) : nullptr; }
template<typename T, typename U> requires(requires(T v) { { v == nullptr } -> std::convertible_to<bool>; })
[[gnu::always_inline]]
constexpr decltype(auto) map_null(T&& value, auto&& with, std::add_rvalue_reference_t<U> otherwise) noexcept(std::is_nothrow_invocable_v<decltype(with), decltype(std::forward<T>(value))> && (!std::is_invocable_v<U> || std::is_nothrow_invocable_v<decltype(otherwise)>)) {
if (!bool(value == nullptr)) return with(std::forward<T>(value));
else if constexpr(std::is_invocable_v<U>) return otherwise();
else return std::forward<decltype(otherwise)>(otherwise);
}
template<typename T, typename... Types>
constexpr inline bool is_same_any_v = (std::is_same_v<T, Types> || ...);
constexpr inline auto identity = [] <typename T> (T v) noexcept { return std::forward<T>(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<typename T>
concept AsOption = (std::is_void_v<T>
|| requires{ requires(sizeof(T) == sizeof(T)); }
)
&& !_details::is_same_any_v<T, none_t, std::nullptr_t>;
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<AsOption T>
class Option final {
using value_type_correct = _details::remove_ref_keep_const_t<T>;// std::remove_cvref_t<T>;
constexpr static inline bool is_const_v = std::is_const_v<value_type_correct>;
template<typename U, typename V = const std::remove_const_t<U> >
using correct_const_t = std::conditional_t<is_const_v, V, U>;
public:
[[gnu::const]]
constexpr bool is_mutable() const noexcept { return !is_const_v; }
using value_type = std::remove_const_t<value_type_correct>;
using pointer_type = correct_const_t<value_type*, value_type const*>;
using const_pointer_type = value_type const*;
using reference_type = correct_const_t<value_type*, value_type const&>;
using const_reference_type = value_type const&;
using move_reference_type = std::add_rvalue_reference_t<value_type_correct>;
using const_move_reference_type = value_type const&&;
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<T>;
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<T>&& opt) noexcept requires(!std::is_void_v<T>)
: Option(nullptr) {
if(opt) inner_ = decltype(inner_)(std::move(*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<T>) {}
constexpr Option(const Option& copy) noexcept(std::is_nothrow_copy_constructible_v<decltype(copy.inner_)>)
: inner_(copy.inner_)
{}
constexpr Option(Option&& move) noexcept(std::is_nothrow_move_constructible_v<decltype(move.inner_)>)
: inner_(std::move(move.inner_))
{}
constexpr explicit(std::convertible_to<std::add_rvalue_reference_t<T>, Option<T>>)
Option(_details::rv_ref_t<T> value)
noexcept(std::is_nothrow_invocable_v<convert_to_held_rv, std::add_rvalue_reference_t<T>>)
requires(!std::is_void_v<T>)
: inner_(convert_to_held_rv(std::forward<decltype(value)>(value)))
{}
constexpr explicit Option(pointer_type const&& ptr) noexcept(std::is_nothrow_move_constructible_v<decltype(*ptr)>) requires(!std::is_void_v<T>)
Option(nullptr) {
if(ptr) inner_ = decltype(inner_)(convert_to_held_rv(std::move(*ptr)));
}
constexpr explicit Option(const_pointer_type ptr) noexcept(std::is_nothrow_copy_constructible_v<decltype(*ptr)>) requires(!std::is_void_v<T>)
: Option(nullptr) {
if(ptr) inner_ = decltype(inner_)(convert_to_held_rv(auto(*ptr)));
}
template<typename P> requires(!std::is_void_v<T> && (std::is_same_v<std::remove_cvref_t<P>, pointer_type>
or std::is_same_v<std::remove_cvref_t<P>, const_pointer_type>))
constexpr Option<T> from_pointer(P ptr) noexcept
{ if constexpr(std::is_const_v<decltype(*ptr)> or !std::is_rvalue_reference_v<P>)
return Option<T> { static_cast<const_pointer_type>(ptr) };
else return Option<T> { std::move(static_cast<pointer_type>(ptr)) }; }
//: inner_(ptr ? convert_to_held_rv(*ptr) : nullptr) {}
//TODO: copy/move assign where appropriate
constexpr Option& operator=(const Option& copy) noexcept(std::is_void_v<T> || std::is_nothrow_copy_constructible_v<decltype(copy.inner_)::held_type>)
{
if constexpr(std::is_void_v<T>) {
inner_.has = copy.inner_.has;
} else {
if(this != std::addressof(copy)) {
using inner_t = std::remove_reference_t<decltype(inner_)>;
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<T> || std::is_nothrow_move_constructible_v<decltype(move.inner_)::held_type>)
{
if constexpr(std::is_void_v<T>) std::exchange(inner_.has, move.inner_.has);
else {
if(this != std::addressof(move)) {
using inner_t = std::remove_reference_t<decltype(inner_)>;
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<decltype(std::declval<Option<T>>().inner_)::held_type>)
{
using inner_t = std::remove_reference_t<decltype(inner_)>;
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<T>)
constexpr explicit(std::convertible_to<std::add_rvalue_reference_t<T>, Option<T>>) Option& operator=(_details::rv_ref_t<T> value) noexcept(std::is_void_v<T> || std::is_nothrow_constructible_v<decltype(std::declval<Option<T>>().inner_), std::add_rvalue_reference_t<std::invoke_result_t<decltype(convert_to_held_rv), std::add_rvalue_reference_t<T>>>>) requires(!std::is_void_v<T>)
{
using inner_t = std::remove_reference_t<decltype(inner_)>;
if constexpr (!std::is_void_v<T>)
inner_ = inner_t { convert_to_held_rv(std::forward<decltype(value)>(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<T> held_type;
typedef _null_opt_impl::used_type<T> type;
//struct none_type {};
typedef none_t none_type;
constexpr _impl_base() noexcept = default;
constexpr ~_impl_base() noexcept = default;
};
template<typename _U = T, bool O = is_null_optimised> struct _impl;
template<typename _U, bool O> struct _impl_type { using type = _impl<_U, O>; };
template<bool O> struct _impl_type<void, O> {
static_assert(O, "void should be `null_opt`");
/// std::is_void_v<T>
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<T> == true
template<typename _U> struct alignas(_U) _impl<_U, true> final : public _impl_base {
using type_info = null_optimise<T>;
using held_type = _impl_base::held_type;
using none_type = _impl_base::none_type;
static_assert(has_null_opt<T>, "Invalid use of Option::_impl<true> where has_null_opt<T> == false");
constexpr _impl(std::nullptr_t) noexcept : _impl_base(), value(type_info::sentinel) {}
constexpr _impl(std::add_rvalue_reference_t<held_type> value) noexcept(std::is_nothrow_move_constructible_v<held_type>)
: _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<type_info::type> value) noexcept(std::is_nothrow_constructible_v<held_type, std::invoke_result_t<type_info::convert_to_held, decltype(value)>> && std::is_nothrow_invocable_v<type_info::convert_to_held, decltype(value)) requires(!std::is_same_v<type_info::type, held_type>)
: _impl_base()
, value(type_info::convert_to_held(std::forward<decltype(value)>(value)))
{}*/
constexpr _impl& operator=(std::add_rvalue_reference_t<held_type> value) noexcept(std::is_nothrow_move_constructible_v<held_type>)
{
std::exchange(value, std::forward<decltype(value)>(value));
return *this;
}
constexpr _impl& operator=(none_t) noexcept(std::is_nothrow_destructible_v<held_type>)
{
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<held_type>) {}
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<T> == false
template<typename _U> 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<held_type> value)
noexcept(std::is_nothrow_move_constructible_v<held_type>)
: _impl_base()
, has(true)
, some(std::move(value))
{}
constexpr _impl& operator=(std::add_rvalue_reference_t<held_type> value) noexcept(std::is_nothrow_move_constructible_v<held_type> && std::is_nothrow_destructible_v<held_type>)
{
if(has) std::exchange(some, std::forward<decltype(value)>(value));
else {
none.~none_type();
if constexpr(noexcept(std::is_nothrow_move_constructible_v<held_type>))
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<held_type>)
{
if(has) {
some.~held_type();
none = none_type{};
has = false;
}
return *this;
}
constexpr ~_impl() noexcept(std::is_nothrow_destructible_v<held_type>) {
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<T, is_null_optimised>::type inner_;
// --- `held_type` convertions
constexpr static decltype(auto) convert_to_held_rv(_details::rv_ref_t<T> from) noexcept(!is_null_optimised || std::is_nothrow_invocable_v<null_optimise<T>::convert_to_held, _details::rv_ref_t<T>>) { if constexpr(is_null_optimised) {
return null_optimise<T>::convert_to_held(std::forward<T>(from));
} else {
return std::forward<decltype(from)>(from);
} }
constexpr static decltype(auto) convert_to_held_cv(_details::cv_ref_t<T> from) noexcept(!is_null_optimised || std::is_nothrow_invocable_v<null_optimise<T>::convert_to_held, _details::cv_ref_t<T>>) { if constexpr(is_null_optimised) {
return null_optimise<T>::convert_to_held(from);
} else {
return std::forward<decltype(from)>(from);
} }
using this_held_type = typename std::remove_reference_t<
decltype(std::declval<Option<T>>()
.inner_)>
::held_type;
constexpr static decltype(auto) convert_from_held_cv(
_details::cv_ref_t<this_held_type> held) noexcept(!is_null_optimised || std::is_nothrow_invocable_v<null_optimise<T>::convert_from_held, _details::cv_ref_t<this_held_type>>) { if constexpr(is_null_optimised) {
return null_optimise<T>::convert_from_held(std::forward<decltype(held)>(held));
} else {
return std::forward<decltype(held)>(held);
} }
constexpr static decltype(auto) convert_from_held_ref(
_details::mut_ref_t<this_held_type> held) noexcept(!is_null_optimised || std::is_nothrow_invocable_v<null_optimise<T>::convert_from_held, _details::mut_ref_t<this_held_type>>) { if constexpr(is_null_optimised) {
return null_optimise<T>::convert_from_held(std::forward<decltype(held)>(held));
} else {
return std::forward<decltype(held)>(held);
} }
constexpr static decltype(auto) convert_from_held_rv(
_details::rv_ref_t<this_held_type> held) noexcept(!is_null_optimised || std::is_nothrow_invocable_v<null_optimise<T>::convert_from_held, _details::rv_ref_t<this_held_type>>) { if constexpr(is_null_optimised) {
return null_optimise<T>::convert_from_held(std::forward<decltype(held)>(held));
} else {
return std::forward<decltype(held)>(held);
} }
// ---
};
template<typename T, typename U> requires(requires(T&& p) { static_cast<U&&>(p); })
constexpr Option<U> static_opt_cast(Option<T>&& p) noexcept
{
if(bool(p)) return Option<U> { static_cast<U&&>(std::move(*p)) };
return Option<U> { nullptr };
}
template<typename T, typename U> requires(requires(T&& p) { static_cast<U&&>(p); })
constexpr Option<U> dynamic_opt_cast(Option<T>&& p)
{
if(T* ptr = p.try_get_value())
if(U* pv = dynamic_cast<U*>(ptr))
return Option<U> { std::move(*pv) };
return Option<U> { nullptr };
}
static_assert(sizeof(Option<void*>) > sizeof(void*), "Option<T*>: Bad null_opt size");
static_assert(alignof(Option<void*>) == alignof(void*), "Option<T*>: Bad null_opt align");
static_assert(sizeof(Option<ptr::NonNull<int>>) == sizeof(int*), "Option<NonNull<T>>: Bad null_opt size");
static_assert(alignof(Option<ptr::NonNull<int>>) == alignof(int*), "Option<NonNull<T>>: Bad null_opt align");
static_assert(sizeof(Option<ptr::Unique<int>>) == sizeof(int*), "Option<Unique<T>>: Bad null_opt size");
static_assert(alignof(Option<ptr::Unique<int>>) == alignof(int*), "Option<Unique<T>>: Bad null_opt align");
static_assert(sizeof(Option<double&>) == sizeof(double&), "Option<T&>: Bad null_opt size");
static_assert(alignof(Option<double&>) == alignof(double*), "Option<T&>: Bad null_opt align");
static_assert(sizeof(Option<long long int&&>) == sizeof(char*), "Option<T&&>: Bad null_opt size");
static_assert(alignof(Option<long long int&&>) == alignof(long*), "Option<T&&>: Bad null_opt align");
static_assert(alignof(Option<int>) == alignof(int), "Option<int>: Bad non null_opt align");
static_assert(sizeof(Option<void>) == sizeof(bool), "Option<void>: Bad size");
static_assert(alignof(Option<void>) == alignof(bool), "Option<void>: Bad align");
}
using optional::Option;
}