From 67c553903b93769945e51c7dfa1d854f7ddc1e59 Mon Sep 17 00:00:00 2001 From: Avril Date: Thu, 20 Apr 2023 08:38:25 +0100 Subject: [PATCH] Added `optional` (port from fuxx) for Option, etc. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Started `either`: `Cow` Rust-like copy-on-write reference/owned-value wrapper. Fortune for libexopt's current commit: Half curse − 半凶 --- include/either.hh | 144 ++++++++++++ include/error.hh | 31 +++ include/exopt.h | 4 +- include/optional.h | 548 +++++++++++++++++++++++++++++++++++++++++++++ include/pointer.h | 204 +++++++++++++++++ include/util.hh | 6 + 6 files changed, 936 insertions(+), 1 deletion(-) create mode 100644 include/either.hh create mode 100644 include/error.hh create mode 100644 include/optional.h create mode 100644 include/pointer.h diff --git a/include/either.hh b/include/either.hh new file mode 100644 index 0000000..3c7979b --- /dev/null +++ b/include/either.hh @@ -0,0 +1,144 @@ +#pragma once + +namespace exopt::types { + namespace either [[gnu::visibility("internal")]] { + //TODO: A version with std::shared_ptr instead + template//, typename P = T*> + class Cow final { + using var_t = std::variant; // Owned + public: + //TODO: Use pointer traits of P to deduce `pointer_type`, so we can have P be a shared_ptr too. Or perhaps, a specialisation for `Cow>` would be better + using value_type = std::remove_cvref_t; + + using reference_type = value_type &; + using const_reference_type = value_type const&; + using move_reference_type = value_type &&; + using pointer_type = value_type *; + using const_pointer_type = value_type const*; + + constexpr Cow(std::add_rvalue_reference_t value) noexcept(std::is_nothrow_constructible_v) + : m_value{std::move(value)} {} + + constexpr explicit(std::is_convertible_v, pointer_type>) + Cow(pointer_type p) noexcept + : m_value{p} {} + constexpr explicit Cow(std::nullptr_t) noexcept + : Cow(std::move(static_cast(nullptr))) {} + + constexpr Cow(Cow const& copy) noexcept + : m_value(copy.get()) {} + constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v) + : m_value(std::move(move.m_value)) { move.clear(); /* XXX: Is this needed? I think it is, to ensure that Cow knows if it's been moved, even though std::variant already knows */ } + + /// Return a newly copy-constructed owned object `T` from the referred (or held) value. + constexpr Cow clone() const noexcept(std::is_nothrow_copy_constructible_v) requires(std::is_copy_constructible_v) + { + if(const auto* pref = this->get()) + return { *pref }; + else return { nullptr }; + } + //TODO: into_owned(){, const}, to_owned(){, const}{&, &&}, etc.. + //TODO: Operator overloads for access to `value_type`: operator*, operator->, etc. + + constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v) { + if(this != std::addressof(move)) { + if(__builtin_expect(move.is_empty(), false)) clear(); + else { + m_value.emplace(std::move(move).as_ref()) + move.clear(); + } + } return *this; + } + constexpr Cow(Cow const& copy) noexcept(std::is_nothrow_destructible_v) { + if(this != std::addressof(copy)) { + m_value.emplace(copy.get()); + } return *this; + } + + constexpr ~Cow() noexcept(std::is_nothrow_destructible_v) = default; + + constexpr move_reference_type as_ref() && { + std::visit([](auto&& arg) -> move_reference_type { + using U = std::remove_cvref_t; + if constexpr(std::is_same_v reference_type { + using U = std::remove_cvref_t; + if constexpr(std::is_same_v(arg); + else return *arg; + }, m_value); + } + constexpr const_reference_type as_ref() const { + std::visit([](const auto& arg) -> const_reference_type { + using U = std::remove_cvref_t; + if constexpr(std::is_same_v(arg); + else return *arg; + }, m_value); + } + //TODO: ^ is_empty() null-checks for `*arg`. + + constexpr const_pointer_type get() const noexcept { + std::visit([](const auto& arg) -> const_pointer_type { + using U = std::remove_cvref_t; + if constexpr(std::is_same_v pointer_type { + using U = std::remove_cvref_t; + if constexpr(std::is_same_v(&m_value)) + if(!p_ref) return true; + return false; + } + constexpr void clear() noexcept(std::is_nothrow_destructible_v) { + m_value.emplace(nullptr); + } + private: + var_t m_value; + }; + + template + class Cow> final { + using var_t = std::variant< + std::weak_ptr // Borrowed + , std::shared_ptr>; // Owned + public: + using value_type = std::remove_cvref_t; + + using reference_type = value_type &; + using const_reference_type = value_type const&; + using move_reference_type = value_type &&; + using pointer_type = value_type; + using const_pointer_type = value_type const*; + //TODO: AutoCow: See above comment about RC'd lifetimes with shallow coppies. + // How should we implement this... I think weak for borrowed and shared for owned is good, since we can still use shared_ptr's aliasing ctor to apply clone() (copy-construction) to the `value_type` directly while keeping the ref-count. + // XXX: Therfore, there should never be *any* strictly-aliasing variants both exposing the same `value_type`, despite *all* the variants managing the lifetime of the `value_type`. I think... Hnm... Can we get shared_ptr's aliasing constructor to *expose* a newly constructed `value_type` while managing the new one and the old one? XXX: Should we even manage the old one at all after a `clone()`? I actually don't think we should, since `clone()` should detach lifetime from the instance that it was called since it's a newly constructed (owned) object. + + private: + var_t m_value; + }; + } + + /// Manually lifetime managed Cow + using either::Cow; + + /// RC-lifetime managed Cow + template + using AutoCow = Cow>; +} diff --git a/include/error.hh b/include/error.hh new file mode 100644 index 0000000..458cbd6 --- /dev/null +++ b/include/error.hh @@ -0,0 +1,31 @@ +#pragma once + +#include "exopt.h" + +#include "optional.h" +#include "util.hh" + + +namespace exopt { + class Report { + virtual ~Report(); + }; + class Error { + constexpr Error() = default; + constexpr Error(Error const&) = default; + constexpr Error(Error &&) = default; + constexpr Error& operator=(Error &&) = default; + constexpr Error& operator=(Error const&) = default; + + constexpr virtual std::string_view message() const noexcept =0; + + constexpr virtual MaybeOwnedRef inner() noexcept { return {}; } //TODO: Maybe just use `std::optional>` + constexpr virtual MaybeOwned into_report() noexcept { /* TODO */ } // XXX: ^^ + constexpr virtual MaybeOwned into_report() && noexcept { /* TODO: auto{*this}.into_report() */ } + + constexpr virtual ~Error() = default; + protected: + + private: + }; +} diff --git a/include/exopt.h b/include/exopt.h index aecc0a6..7ef0b7a 100644 --- a/include/exopt.h +++ b/include/exopt.h @@ -1,6 +1,8 @@ #ifndef _EXOPT_H #define _EXOPT_H +#define _EO__OPT_OLD_ASSUME //XXX: __attribute__((gnu::assume(X))) fails to build + #define _EXOPT_INTERNAL_PREFIX _exopt__ #ifdef __cplusplus @@ -27,7 +29,7 @@ extern "C" { #if _EO(cpp) # ifndef _EO_ASSUME -# define _EO_ASSUME(X) [[assume(X)]] +# define _EO_ASSUME(X) [[gnu::assume(X)]] # endif # define _exopt__restrict __restrict__ #else diff --git a/include/optional.h b/include/optional.h new file mode 100644 index 0000000..c4b92eb --- /dev/null +++ b/include/optional.h @@ -0,0 +1,548 @@ +#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; +} diff --git a/include/pointer.h b/include/pointer.h new file mode 100644 index 0000000..7f4eaaf --- /dev/null +++ b/include/pointer.h @@ -0,0 +1,204 @@ +#pragma once + +#include +#include +#include + +namespace exopt::ptr { + template + using RawPtr = T*; + template + using RawRef = T&; + + template + using UniquePtr = T* __restrict__; + template + using UniqueRef = T& __restrict__; + + template + using AliasedPtr = T __attribute__((__may_alias__))*; + template + using AliasedRef = T __attribute__((__may_alias__))&; + + template + concept IsSize = sizeof(T) == Size; + + template + concept PointerEqCmp = requires(T p) { + { p == nullptr } -> std::convertible_to; + }; + + template + concept PointerMemCmp = PointerEqCmp && requires(T p, T p2) { + { p == p2 } -> std::convertible_to; + { static_cast(p) }; + }; + + template + concept PointerDeref = PointerEqCmp && requires(T p) { + { *p }; + }; + template + concept PointerDerefTo = PointerDeref && requires(T p) { + { *p } -> std::convertible_to; + }; + + template + constexpr decltype(auto) map(T&& ptr, auto&& func) noexcept(std::is_nothrow_invocable_v) requires(std::is_invocable_v) + { + if (ptr == nullptr) return std::forward(ptr); + else return func(std::forward(ptr)); + } + + template + constexpr decltype(auto) map(T&& ptr, auto&& func, auto&& other) noexcept(std::is_nothrow_invocable_v && (!std::is_invocable_v || std::is_nothrow_invocable_v)) requires(std::is_invocable_v) + { + if (ptr == nullptr) return std::forward(ptr); + else if constexpr(std::is_invocable_v) return other(); + else return std::forward(other); + } + + template + constexpr decltype(auto) not_null_or(T&& ptr, auto&& other) noexcept(!std::is_invocable_v) requires(std::is_convertible_v || (std::is_invocable_v && std::is_convertible_v, decltype(ptr)>)) + { + if(!(ptr == nullptr)) return std::forward(ptr); + else if constexpr(std::is_invocable_v) return other(); + else return std::forward(other); + } + + template + constexpr bool addr_eq(const T& o1, const U& o2) noexcept { return static_cast(std::addressof(o1)) == static_cast(std::addressof(o2)); } + + template + constexpr bool ptr_addr_eq(const PointerMemCmp auto& ptr, const U& obj) noexcept { return static_cast(ptr) == static_cast(std::addressof(obj)); } + + template + constexpr decltype(auto) deref_or(U&& ptr, auto&& other) noexcept(noexcept(*ptr) && (!std::is_invocable_v || std::is_nothrow_invocable_v)) { + if (!(ptr == nullptr)) return *ptr; + else if constexpr (std::is_invocable_v) return other(); + else return std::forward(other); + } + + template requires(std::is_constructible_v) + constexpr decltype(auto) throw_if_null(PointerEqCmp auto&& ptr, Args&&... error) { + auto _throw = [&] [[noreturn, gnu::noinline, gnu::cold]] () { + throw E{std::forward(error)...}; + }; + if(ptr == nullptr) _throw(); + return std::forward(ptr); + } + + template requires(std::is_constructible_v) + constexpr decltype(auto) deref_or_throw(PointerDeref auto&& ptr, Args&&... error) + { + return *throw_if_null(std::forward(ptr), std::forward(error)...); + } + + template + constexpr auto read_once(const T& ref) noexcept(std::is_nothrow_copy_constructible_v) requires(std::is_copy_constructible_v) + { + AliasedPtr aref = std::addressof(ref); + return *aref; + } + + template + constexpr decltype(auto) write_once(T& ref, std::convertible_to auto&& value) noexcept(std::is_nothrow_move_constructible_v) requires(std::is_move_constructible_v && std::is_constructible_v) + { + AliasedPtr aref = std::addressof(ref); + return *aref = std::move(value); + + } + + template + constexpr T read_once_strict(const T& ref) noexcept(std::is_nothrow_copy_constructible_v) requires(std::is_copy_constructible_v) + { + return *static_cast(std::addressof(ref)); + } + + template + constexpr void write_once_strict(T& ref, std::convertible_to auto&& value) noexcept(std::is_nothrow_move_constructible_v) requires(std::is_move_constructible_v && std::is_constructible_v) + { + *static_cast(std::addressof(ref)) = T{std::move(value)}; + } + + template requires(std::is_constructible_v) + constexpr T& leak(Args&&... ctor) noexcept(std::is_nothrow_constructible_v) + { + return *new T(std::forward(ctor)...); + } + + struct NullException /*TODO : public error::Error*/ { + constexpr NullException() noexcept = default; + constexpr NullException(const NullException&) noexcept = default; + constexpr NullException& operator=(const NullException&) noexcept = default; + constexpr virtual ~NullException() noexcept {} + + constexpr std::string_view message() const noexcept /*override*/ { return "pointer was null"; } + }; + + template requires(!std::is_reference_v) + struct NonNull final { + constexpr NonNull(T& ref) noexcept + : ptr_(std::addressof(ref)) {} + [[gnu::nonnull(1, 2)]] + constexpr explicit NonNull(T* ptr) noexcept + : ptr_(ptr) {} + constexpr NonNull(const NonNull&) noexcept = default; + constexpr NonNull(NonNull&&) noexcept = default; + constexpr NonNull& operator=(const NonNull&) noexcept = default; + constexpr NonNull& operator=(NonNull&&) noexcept = default; + constexpr ~NonNull() noexcept = default; + + constexpr static NonNull try_new(T* ptr) { + constexpr auto _throw = [] [[noreturn, gnu::noinline, gnu::cold]] () { + if consteval { + throw NullException{}; + } else { + throw std::runtime_error(NullException{}.message()); + } + //throw error::as_runtime_error(error::comptime_fail()); + }; + if(!ptr) _throw(); + return NonNull{ptr}; + } +#ifdef DEBUG + [[gnu::nonnull(1)]] +#endif + constexpr static NonNull new_unchecked(T* ptr) noexcept { +#ifndef DEBUG + _EO_ASSUME(ptr!=nullptr); +#endif + return NonNull{ptr}; } + + constexpr friend bool operator==(std::nullptr_t, const NonNull&) noexcept { return false; } + constexpr friend bool operator==(const NonNull&, std::nullptr_t) noexcept { return false; } + + constexpr friend bool operator!=(std::nullptr_t, const NonNull&) noexcept { return true; } + constexpr friend bool operator!=(const NonNull&, std::nullptr_t) noexcept { return true; } + + constexpr friend auto operator<=>(const NonNull& a, const NonNull& b) noexcept { return a.ptr_ <=> b.ptr_; } + constexpr friend auto operator<=>(const NonNull& a, T* b) noexcept { return (!b) ? (true<=>false) : a.ptr_ <=> b; } + constexpr friend auto operator<=>(T* a, const NonNull& b) noexcept { return (!a) ? (false<=>true) : (a <=> b.ptr_); } + + [[gnu::returns_nonnull]] + constexpr T* get() const noexcept { return ptr_; } +/* + [[gnu::returns_nonnull]] + constexpr T* const&& get() const&& noexcept { return std::move(ptr_); } +*/ + [[gnu::returns_nonnull]] + constexpr operator T*() const noexcept { return ptr_; } + + constexpr T& operator*() & noexcept { return *ptr_; } + constexpr const T& operator*() const& noexcept { return *ptr_; } + constexpr T&& operator*() && noexcept { return std::move(*ptr_); } + constexpr decltype(auto) operator*() const&& noexcept { return *ptr_; } + + [[gnu::returns_nonnull]] + constexpr T* operator->() noexcept { return ptr_; } + [[gnu::returns_nonnull]] + constexpr const T* operator->() const noexcept { return ptr_; } + private: + T* ptr_; + }; +} diff --git a/include/util.hh b/include/util.hh index 033a5dc..442efa5 100644 --- a/include/util.hh +++ b/include/util.hh @@ -1,11 +1,14 @@ #pragma once +#include #include #include #include #include "exopt.h" +#include "optional.h" + #define _EO_CONSTANT_VALUE(X) ([]() { \ struct { \ typedef decltype(X) comptime_constant_t; \ @@ -20,6 +23,9 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] { typename T::comptime_constant_t; }; + template + concept is_complete = requires{ { sizeof(T) == sizeof(T) }; }; + constexpr auto comptime_value(auto value) noexcept { using U = decltype(value); struct inner {