|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
|
|
#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<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> 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 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>
|
|
|
|
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<T>;
|
|
|
|
|
|
|
|
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)))
|
|
|
|
{}
|
|
|
|
|
|
|
|
//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);
|
|
|
|
} }
|
|
|
|
|
|
|
|
// ---
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|