Started `either`: `Cow<T>` Rust-like copy-on-write reference/owned-value wrapper. Fortune for libexopt's current commit: Half curse − 半凶boxed_is_boxed_value
parent
807008d965
commit
67c553903b
@ -0,0 +1,144 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace exopt::types {
|
||||||
|
namespace either [[gnu::visibility("internal")]] {
|
||||||
|
//TODO: A version with std::shared_ptr<T> instead
|
||||||
|
template<typename T>//, typename P = T*>
|
||||||
|
class Cow final {
|
||||||
|
using var_t = std::variant<pointer_type // Borrowed
|
||||||
|
, value_type>; // 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<std::shared_ptr<T>>` would be better
|
||||||
|
using value_type = std::remove_cvref_t<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<T> value) noexcept(std::is_nothrow_constructible_v<value_type, decltype(value)>)
|
||||||
|
: m_value{std::move(value)} {}
|
||||||
|
|
||||||
|
constexpr explicit(std::is_convertible_v<std::add_rvalue_reference<T>, pointer_type>)
|
||||||
|
Cow(pointer_type p) noexcept
|
||||||
|
: m_value{p} {}
|
||||||
|
constexpr explicit Cow(std::nullptr_t) noexcept
|
||||||
|
: Cow(std::move(static_cast<pointer_type>(nullptr))) {}
|
||||||
|
|
||||||
|
constexpr Cow(Cow const& copy) noexcept
|
||||||
|
: m_value(copy.get()) {}
|
||||||
|
constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v<value_type>)
|
||||||
|
: m_value(std::move(move.m_value)) { move.clear(); /* XXX: Is this needed? I think it is, to ensure that Cow<T> 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<value_type>) requires(std::is_copy_constructible_v<T>)
|
||||||
|
{
|
||||||
|
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<value_type>) {
|
||||||
|
if(this != std::addressof(move)) {
|
||||||
|
if(__builtin_expect(move.is_empty(), false)) clear();
|
||||||
|
else {
|
||||||
|
m_value.emplace<value_type>(std::move(move).as_ref())
|
||||||
|
move.clear();
|
||||||
|
}
|
||||||
|
} return *this;
|
||||||
|
}
|
||||||
|
constexpr Cow(Cow const& copy) noexcept(std::is_nothrow_destructible_v<value_type>) {
|
||||||
|
if(this != std::addressof(copy)) {
|
||||||
|
m_value.emplace<pointer_type>(copy.get());
|
||||||
|
} return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr ~Cow() noexcept(std::is_nothrow_destructible_v<value_type>) = default;
|
||||||
|
|
||||||
|
constexpr move_reference_type as_ref() && {
|
||||||
|
std::visit([](auto&& arg) -> move_reference_type {
|
||||||
|
using U = std::remove_cvref_t<decltype(arg)>;
|
||||||
|
if constexpr(std::is_same_v<U, value_type)
|
||||||
|
return std::move(arg);
|
||||||
|
else return std::move(*arg);
|
||||||
|
}, m_value);
|
||||||
|
}
|
||||||
|
constexpr reference_type as_ref() & {
|
||||||
|
std::visit([](auto& arg) -> reference_type {
|
||||||
|
using U = std::remove_cvref_t<decltype(arg)>;
|
||||||
|
if constexpr(std::is_same_v<U, value_type)
|
||||||
|
return std::forward<decltype(arg)>(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<decltype(arg)>;
|
||||||
|
if constexpr(std::is_same_v<U, value_type)
|
||||||
|
return std::forward<decltype(arg)>(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<decltype(arg)>;
|
||||||
|
if constexpr(std::is_same_v<U, value_type)
|
||||||
|
return std::addressof(arg);
|
||||||
|
else return arg;
|
||||||
|
}, m_value);
|
||||||
|
}
|
||||||
|
constexpr pointer_type get() noexcept {
|
||||||
|
std::visit([](auto& arg) -> pointer_type {
|
||||||
|
using U = std::remove_cvref_t<decltype(arg)>;
|
||||||
|
if constexpr(std::is_same_v<U, value_type)
|
||||||
|
return std::addressof(arg);
|
||||||
|
else return arg;
|
||||||
|
}, m_value);
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
constexpr bool is_empty() const noexcept {
|
||||||
|
if(const const_pointer_type* p_ref = std::get_if<pointer_type>(&m_value))
|
||||||
|
if(!p_ref) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
constexpr void clear() noexcept(std::is_nothrow_destructible_v<value_type>) {
|
||||||
|
m_value.emplace<pointer_type>(nullptr);
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
var_t m_value;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class Cow<std::shared_ptr<T>> final {
|
||||||
|
using var_t = std::variant<
|
||||||
|
std::weak_ptr<value_type> // Borrowed
|
||||||
|
, std::shared_ptr<value_type>>; // Owned
|
||||||
|
public:
|
||||||
|
using value_type = std::remove_cvref_t<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<T>'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<T>
|
||||||
|
using either::Cow;
|
||||||
|
|
||||||
|
/// RC-lifetime managed Cow<T>
|
||||||
|
template<typename T>
|
||||||
|
using AutoCow = Cow<std::shared_ptr<T>>;
|
||||||
|
}
|
@ -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<Error> inner() noexcept { return {}; } //TODO: Maybe just use `std::optional<std::reference_wrapper<Error>>`
|
||||||
|
constexpr virtual MaybeOwned<Report> into_report() noexcept { /* TODO */ } // XXX: ^^
|
||||||
|
constexpr virtual MaybeOwned<Report> into_report() && noexcept { /* TODO: auto{*this}.into_report() */ }
|
||||||
|
|
||||||
|
constexpr virtual ~Error() = default;
|
||||||
|
protected:
|
||||||
|
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,548 @@
|
|||||||
|
#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<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<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;
|
||||||
|
}
|
@ -0,0 +1,204 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <concepts>
|
||||||
|
#include <bit>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
namespace exopt::ptr {
|
||||||
|
template<typename T>
|
||||||
|
using RawPtr = T*;
|
||||||
|
template<typename T>
|
||||||
|
using RawRef = T&;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using UniquePtr = T* __restrict__;
|
||||||
|
template<typename T>
|
||||||
|
using UniqueRef = T& __restrict__;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
using AliasedPtr = T __attribute__((__may_alias__))*;
|
||||||
|
template<typename T>
|
||||||
|
using AliasedRef = T __attribute__((__may_alias__))&;
|
||||||
|
|
||||||
|
template<typename T, size_t Size>
|
||||||
|
concept IsSize = sizeof(T) == Size;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept PointerEqCmp = requires(T p) {
|
||||||
|
{ p == nullptr } -> std::convertible_to<bool>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept PointerMemCmp = PointerEqCmp<T> && requires(T p, T p2) {
|
||||||
|
{ p == p2 } -> std::convertible_to<bool>;
|
||||||
|
{ static_cast<void*>(p) };
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
concept PointerDeref = PointerEqCmp<T> && requires(T p) {
|
||||||
|
{ *p };
|
||||||
|
};
|
||||||
|
template<typename T, typename To>
|
||||||
|
concept PointerDerefTo = PointerDeref<T> && requires(T p) {
|
||||||
|
{ *p } -> std::convertible_to<To>;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<PointerEqCmp T>
|
||||||
|
constexpr decltype(auto) map(T&& ptr, auto&& func) noexcept(std::is_nothrow_invocable_v<decltype(func), decltype(ptr)>) requires(std::is_invocable_v<decltype(func), decltype(ptr)>)
|
||||||
|
{
|
||||||
|
if (ptr == nullptr) return std::forward<T>(ptr);
|
||||||
|
else return func(std::forward<T>(ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<PointerEqCmp T>
|
||||||
|
constexpr decltype(auto) map(T&& ptr, auto&& func, auto&& other) noexcept(std::is_nothrow_invocable_v<decltype(func), decltype(ptr)> && (!std::is_invocable_v<decltype(other)> || std::is_nothrow_invocable_v<decltype(other)>)) requires(std::is_invocable_v<decltype(func), decltype(ptr)>)
|
||||||
|
{
|
||||||
|
if (ptr == nullptr) return std::forward<T>(ptr);
|
||||||
|
else if constexpr(std::is_invocable_v<decltype(other)>) return other();
|
||||||
|
else return std::forward<decltype(other)>(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<PointerEqCmp T>
|
||||||
|
constexpr decltype(auto) not_null_or(T&& ptr, auto&& other) noexcept(!std::is_invocable_v<decltype(other)>) requires(std::is_convertible_v<decltype(other), T> || (std::is_invocable_v<decltype(other)> && std::is_convertible_v<std::invoke_result_t<decltype(other)>, decltype(ptr)>))
|
||||||
|
{
|
||||||
|
if(!(ptr == nullptr)) return std::forward<T>(ptr);
|
||||||
|
else if constexpr(std::is_invocable_v<decltype(other)>) return other();
|
||||||
|
else return std::forward<decltype(other)>(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename U>
|
||||||
|
constexpr bool addr_eq(const T& o1, const U& o2) noexcept { return static_cast<void*>(std::addressof(o1)) == static_cast<void*>(std::addressof(o2)); }
|
||||||
|
|
||||||
|
template<typename U>
|
||||||
|
constexpr bool ptr_addr_eq(const PointerMemCmp auto& ptr, const U& obj) noexcept { return static_cast<void*>(ptr) == static_cast<void*>(std::addressof(obj)); }
|
||||||
|
|
||||||
|
template<PointerDeref U>
|
||||||
|
constexpr decltype(auto) deref_or(U&& ptr, auto&& other) noexcept(noexcept(*ptr) && (!std::is_invocable_v<decltype(other)> || std::is_nothrow_invocable_v<decltype(other)>)) {
|
||||||
|
if (!(ptr == nullptr)) return *ptr;
|
||||||
|
else if constexpr (std::is_invocable_v<decltype(other)>) return other();
|
||||||
|
else return std::forward<decltype(other)>(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename E, typename... Args> requires(std::is_constructible_v<E, Args...>)
|
||||||
|
constexpr decltype(auto) throw_if_null(PointerEqCmp auto&& ptr, Args&&... error) {
|
||||||
|
auto _throw = [&] [[noreturn, gnu::noinline, gnu::cold]] () {
|
||||||
|
throw E{std::forward<Args>(error)...};
|
||||||
|
};
|
||||||
|
if(ptr == nullptr) _throw();
|
||||||
|
return std::forward<decltype(ptr)>(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename E, typename... Args> requires(std::is_constructible_v<E, Args...>)
|
||||||
|
constexpr decltype(auto) deref_or_throw(PointerDeref auto&& ptr, Args&&... error)
|
||||||
|
{
|
||||||
|
return *throw_if_null<E, Args...>(std::forward<decltype(ptr)>(ptr), std::forward<Args>(error)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
constexpr auto read_once(const T& ref) noexcept(std::is_nothrow_copy_constructible_v<T>) requires(std::is_copy_constructible_v<T>)
|
||||||
|
{
|
||||||
|
AliasedPtr<const T> aref = std::addressof(ref);
|
||||||
|
return *aref;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
constexpr decltype(auto) write_once(T& ref, std::convertible_to<T> auto&& value) noexcept(std::is_nothrow_move_constructible_v<T>) requires(std::is_move_constructible_v<decltype(value)> && std::is_constructible_v<T, decltype(std::move(value))>)
|
||||||
|
{
|
||||||
|
AliasedPtr<T> aref = std::addressof(ref);
|
||||||
|
return *aref = std::move(value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
constexpr T read_once_strict(const T& ref) noexcept(std::is_nothrow_copy_constructible_v<T>) requires(std::is_copy_constructible_v<T>)
|
||||||
|
{
|
||||||
|
return *static_cast<const volatile T*>(std::addressof(ref));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
constexpr void write_once_strict(T& ref, std::convertible_to<T> auto&& value) noexcept(std::is_nothrow_move_constructible_v<T>) requires(std::is_move_constructible_v<decltype(value)> && std::is_constructible_v<T, decltype(std::move(value))>)
|
||||||
|
{
|
||||||
|
*static_cast<volatile T*>(std::addressof(ref)) = T{std::move(value)};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T, typename... Args> requires(std::is_constructible_v<T, Args...>)
|
||||||
|
constexpr T& leak(Args&&... ctor) noexcept(std::is_nothrow_constructible_v<T, Args...>)
|
||||||
|
{
|
||||||
|
return *new T(std::forward<Args>(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<typename T> requires(!std::is_reference_v<T>)
|
||||||
|
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<T> 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<NullException>());
|
||||||
|
};
|
||||||
|
if(!ptr) _throw();
|
||||||
|
return NonNull<T>{ptr};
|
||||||
|
}
|
||||||
|
#ifdef DEBUG
|
||||||
|
[[gnu::nonnull(1)]]
|
||||||
|
#endif
|
||||||
|
constexpr static NonNull<T> 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_;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in new issue