Compare commits

..

3 Commits

Author SHA1 Message Date
Avril 4698b48344
Improving error interface: Begin working on `Box<T>` to allow any `T` to tell if it is owned by a `Box<T>`.
1 year ago
Avril 2d30b3ce85
Adding polymorphic boxing helpers.
1 year ago
Avril 044006aef0
Began rework of Error/TracedError interface.
1 year ago

@ -10,13 +10,14 @@
#include "exopt.h"
namespace exopt::types { namespace boxed {
template<typename T>
struct [[gnu::visibility("internal")]] boxable_value_type { using type = std::conditional_t
<std::is_reference_v<T>
,std::reference_wrapper<std::remove_cvref_t<T> >
,T>; };
namespace {
namespace {
template<typename T>
constexpr std::unique_ptr<T> uniq_clone(std::unique_ptr<T> const& c) noexcept(std::is_nothrow_copy_constructible_v<T>) requires(std::is_copy_constructible_v<T>)
@ -30,7 +31,7 @@ namespace exopt::types { namespace boxed {
requires(std::is_copy_constructible_v<T>)
{ if(__builtin_expect(!bool(c), false))
#ifdef DEBUG
throw ptr::NullDerefException<T>{};
throw ptr::NullException{};
#else
__builtin_unreachable();
#endif
@ -62,94 +63,6 @@ namespace exopt::types { namespace boxed {
{ static_cast<std::decay_t<To> &&>(from) } -> std::convertible_to<To>;
};
/*struct ErasedTypeDeleter {
constexpr virtual ~ErasedTypeDeleter() = default;
constexpr ErasedTypeDeleter() noexcept = default;
constexpr virtual void apply_delete() = 0;
//TODO: See below...
};*/
template<polymorphic_castable<void> T>
constexpr auto uniq_erase_type(std::unique_ptr<T>&& from) noexcept {
struct deleter final /*: public ErasedTypeDeleter XXX <- Is this useful, for unerasing the type, maybe?*/ {
typedef T type;
/*[[gnu::cold, noreturn, gnu::noinline]]
constexpr static void _throw()
{
throw ptr::NullException{};
}
public:*/
constexpr void opreator()(void *p) noexcept(std::is_nothrow_destructible_v<T>) {
/* T* ptr;
if constexpr(DOES_CHECK) {
if(__builtin_expect(!(ptr = dynamic_cast<T*>(p)), false))
_throw();
} else ptr = static_cast<T*>(p);
delete ptr;*/
delete static_cast<T*>(p);
}
constexpr T* back_cast(void *p) noexcept { return static_cast<T*>(p); }
constexpr deleter() noexcept = default;
constexpr deleter(deleter const&) noexcept = default;
constexpr deleter(deleter &&) noexcept = default;
constexpr deleter& operator=(deleter const&) noexcept = default;
constexpr deleter& operator=(deleter &&) noexcept = default;
constexpr ~deleter() noexcept = default;
};
return std::unique_ptr<void, deleter> {
// Cannot retain type information of most-derived class from this: \
dynamic_cast<void*>
static_cast<void*>
(std::move(from).release())
};
}
template<polymorphic_castable<void> T, typename D> requires(requires{
typename std::unique_ptr<T, D>;
requires(!std::is_same_v<std::unique_ptr<T, D>::deleter_type, std::unique_ptr<T>::deleter_type);
})
constexpr auto uniq_erase_type(std::unique_ptr<T, D>&& from) noexcept {
class deleter final {
D m_del;
public:
constexpr void operator()(void* p) noexcept(std::is_nothrow_invocable_v<D::operator(), T>) {
m_del(static_cast<T*>(p));
}
constexpr T* back_cast(void* p) noexcept { return static_cast<T*>(p); }
constexpr deleter(D&& deleter) noexcept(std::is_nothrow_move_constructible_v<D>)
: m_del(std::move(deleter)) {}
constexpr deleter() noexcept(std::is_nothrow_default_constructible_v<D>) requires(std::is_default_constructible_v<D>) =default;//: m_del() {}
constexpr deleter(const deleter&) noexcept(std::is_nothrow_copy_constructible_v<D>) = default;
constexpr deleter(deleter&&) noexcept(std::is_nothrow_move_constructible_v<D>) = default;
constexpr deleter& operator=(const deleter&) noexcept(std::is_nothrow_copy_assignable_v<D>) = default;
constexpr deleter& operator=(deleter&&) noexcept(std::is_nothrow_move_assignable_v<D>) = default;
constexpr ~deleter() noexcept(std::is_nothrow_destructible_v<D>) requires(std::is_trivially_destructible_v<D>) = default;
constexpr ~deleter() noexcept(std::is_nothrow_destructible_v<D>) {}
};
if constexpr(std::is_default_constructible_v<D>)
return std::unique_ptr<void, deleter> {
static_cast<void*>
(std::move(from).release()), deleter()
};
else {
deleter&& del{std::move(from.get_deleter())};
return std::unique_ptr<void, deleter> {
static_cast<void*>
(std::move(from).release()), deleter{std::move(del)}
};
}
}
template<typename To, typename From> requires(polymorphic_castable<From, To>)
constexpr std::unique_ptr<To> static_uniq_cast(std::unique_ptr<From>&& from) noexcept
{
@ -173,8 +86,25 @@ namespace exopt::types { namespace boxed {
else return static_uniq_cast(std::move(from));
}
#if 0
//TODO: Cannot be implemented with this method
constexpr bool is_boxed_value(auto const& value) noexcept {
return bool(dynamic_cast<ObjectBase const*>(std::addressof(value)));
}
template<typename T>
constexpr bool is_boxed_type() noexcept {
using type = std::remove_reference_t<T>;
constexpr type const* VALUE = nullptr;
return bool(dynamic_cast<ObjectBase const*>(VALUE));
}
// We need either: Tagged pointers (XOR, bit-mangled, manual heap, or GOT (external allocation) kind), \
or (preferred): layout of Box<T> to be `{ aligned<T>(T, box_type_tag) } unique*` \
box_type_tag should be: One pointer width, starting at offset *T+1 (aligned to T within internal tuple struct), provides a way to access RTTI for `*T`, a pointer to static (non-allocated) type information or dynamic_cast used lambda would be ideal.
#endif
template<typename T>
struct alignas(std::unique_ptr<T>) Box {
struct Box final {
typedef boxable_value_type<T>::type type;
template<std::derived_from<T> U, bool RuntimeCheck=false>
@ -220,24 +150,15 @@ namespace exopt::types { namespace boxed {
}
[[gnu::nonnull(1)]]
constexpr static Box<T> from_raw_ptr(T* ptr) noexcept(!_EO(DEBUG)) {
#if DEBUG
ptr::throw_if_null(ptr);
#endif
constexpr static Box<T> from_raw_ptr(T* ptr) noexcept {
return Box<T>{ std::unique_ptr<T>{ ptr } };
}
constexpr static Box<T> from_raw_ptr(std::unique_ptr<T>&& uniq) {
if(__builtin_expect(!bool(uniq), false)) ptr::throw_null_exception<T>();
_EO_ASSUME(uniq.get() != nullptr);
return Box<T> { std::move(uniq) };
}
constexpr static Box<T> from_raw_ptr_unchecked(std::unique_ptr<T>&& uniq) noexcept(!_EO(DEBUG)) {
constexpr static Box<T> from_raw_ptr(std::unique_ptr<T>&& uniq)
#if DEBUG
if(__builtin_expect(!uniq, false)) ptr::throw_null_exception<T>();
{ if(__builtin_expect(!uniq, false)) throw ptr::NullException{};
#else
noexcept {
_EO_ASSUME(uniq.get() != nullptr);
#endif
return Box<T>{ std::move(uniq) };
@ -252,63 +173,40 @@ namespace exopt::types { namespace boxed {
//TODO: Accessors, `(explicit?) operator std::unique_ptr<T> const&[&]`?, etc.
constexpr ~Box() = default;
protected:
// For types that may need to invalidate the Box<T> guarantee without exposing it in API, they can inherit (non-virtual).
// For unsafe operations, wether or not to apply checks.
enum unsafe_t {
UNSAFE,
};
enum maybe_unsafe_t {
CHECKED,
DEBUG_CHECKED,
};
constexpr Box(util::exact_type<unsafe_t> auto u, std::unique_ptr<T>&& ptr) noexcept
: m_ptr(std::move(ptr)) { (void)u; }
constexpr static Box<T> from_raw_ptr(unsafe_t u, std::unique_ptr<T>&& uniq) noexcept {
return Box{u, std::move(uniq)};
}
constexpr std::unique_ptr<T>& as_unique(unsafe_t) & noexcept { return m_ptr; }
constexpr std::unique_ptr<T> const & as_unique(unsafe_t) const& noexcept { return m_ptr; }
constexpr std::unique_ptr<T>&& as_unique(unsafe_t) && noexcept { return std::move(m_ptr); }
constexpr std::unique_ptr<T> const&& as_unique(unsafe_t) const&& noexcept { return std::move(m_ptr); }
constexpr T* as_unsafe_ptr(unsafe_t) const noexcept { return m_ptr.get(); }
constexpr std::unique_ptr<T> swap_unsafe_ptr(unsafe_t u, T* raw) noexcept
{ return (void)u; std::exchange(std::move(m_ptr), std::unique_ptr<T>{ raw }); }
constexpr void set_unsafe_ptr(unsafe_t 8, T* raw) noexcept {
(void)u;
m_ptr.reset(raw);
}
constexpr decltype(auto) set_unsafe(unsafe_t u, std::unique_ptr<T>&& ptr) noexcept
{ (void)u; return std::exchange(std::move(m_ptr), std::move(ptr)); }
constexpr auto& swap_unsafe(unsafe_t, std::unique_ptr<T>& ptr) noexcept {
using std::swap;
swap(m_ptr, ptr);
return m_ptr;
}
constexpr T* release_unsafe(unsafe_t) noexcept { return m_ptr.release(); }
private:
constexpr explicit Box(std::unique_ptr<T>&& ptr) noexcept
constexpr explicit Box(std::unique_ptr<T>&& ptr)
: m_ptr(std::move(ptr)) {
_EO_ASSUME(m_ptr.get());
}
#if 0
// TODO: Identifying if a value `this` pointer is boxed
struct alignas(type) inner_layout {
constexpr inner_layout() noexcept = default;
constexpr inner_layout(const inner_layout&) noexcept = default;
constexpr ~inner_layout() noexcept = default;
type value;
box_type_tag identifier{box_tag_of<T>()};
};
#endif
std::unique_ptr<T> m_ptr; // Unique<T> m_ptr;
};
static_assert(util::shares_layout<Box<_Complex double>, _Complex double*>, "Invalid Box<T> memory layout: More than just T*");
static_assert(util::shares_layout<Box<_Complex double>, std::unique_ptr<_Complex double>>, "Invalid Box<T> memory layout: More than just std::unique_ptr<T>");
#define _EO_ADD_RV std::add_rvalue_reference_v
template<typename Base, typename T = Base>
constexpr inline bool is_boxable_v = binary_convertible<_EO_ADD_RV<T>, _EO_ADD_RV<Base> > and requires(T&& value) {
typename Box<Base>;
typename Box<Base>::type;
{ std::make_unique<T>(std::move(value)) } -> std::same_as<std::unique_ptr<T>>;
};
template<typename T> concept is_boxable = is_boxable_v<T>;
template<typename T>
constexpr inline bool is_nothrow_boxable_v = is_boxable_v<T> && std::is_nothrow_constructible_v<Box<T>, T>;
#undef _EO_ADD_RV
template<typename T, typename U> requires(requires(T&& o) { static_cast<U&&>(o); })
constexpr Box<U> static_box_cast(Box<T>&& b) noexcept { return Box<U>::from_raw_ptr(static_cast<U*>(b.release())); }
@ -322,12 +220,7 @@ namespace exopt::types { namespace boxed {
delete rel;
throw;
}
}
template<typename T, typename U> requires(requires(T const& o) { static_cast<U const&>(o); })
constexpr Box<U> dynamic_box_cast(Box<T> const& b) {
return Box<U>::from_raw_ptr(std::addressof(dynamic_cast<U const&>(*b)));
}
} //TODO: Overload for `const&` that does the type check *before* the copy allocation.
#if 0
template<typename T, typename R /*XXX: Maybe remove this, and put the requires after the definition, using `auto&& value` instead of `R&& value`*/> requires(std::derived_from<R, T>)
@ -370,8 +263,6 @@ namespace exopt::types { namespace boxed {
using boxed::Box;
}
//TODO: See phone notes on correctly implementing the nullopt for Box<T>
#if 0
namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
template<typename T>
struct null_optimise<boxed::Box<T> > { constexpr static inline bool value = true;
@ -386,4 +277,3 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
}
#endif

@ -11,13 +11,24 @@
#include "util.hh"
#define _EO_DEFEXCEPT_BASIC_(NAME, BASE, ...) class NAME : public ::exopt:: BASE { \
constexpr static inline auto MESSSAGE = (__VA_ARGS__); \
public: \
_EO_CLASS_DEFINE(NAME, constexpr, noexcept, virtual, =default); \
constexpr virtual std::string_view message() const noexcept override { return { MESSAGE }; } \
}
#define _EO_DEFEXCEPT_BASIC(NAME, MSG) _EO_DEFEXCEPT_BASIC_(NAME, Error, MSG)
#define _EO_DEFEXCEPT_TRACE_BASIC(NAME, MSG) _EO_DEFEXCEPT_BASIC_(NAME, TracedError, MSG)
namespace exopt {
// Report generated from an error. (virtual base, not abstract.)
class Report {
public:
virtual ~Report();
};
// This is propagated returned (base, abstract)
// This is base thrown (base, abstract)
class Error {
public:
constexpr Error(Error const&) = default;
@ -25,6 +36,9 @@ namespace exopt {
constexpr Error& operator=(Error &&) = default;
constexpr Error& operator=(Error const&) = default;
virtual types::Box<TracedError> into_traced(std::stacktrace trace&&) &&noexcept;// = std::stacktrace::current()) && noexcept;
inline typed::Box<TracedError> into_traced(std::stacktrace trace = std::stacktrace::current()) && noexcept
{ return std::move(*this).into_traced(std::move(trace)); }
constexpr virtual std::source_location const& location() const noexcept { return m_location; }
@ -32,8 +46,9 @@ namespace exopt {
constexpr virtual std::string_view message() const noexcept =0;
constexpr virtual types::Option<Error&> inner() noexcept { return { std::nullopt }; }
constexpr virtual types::MaybeOwned<Report> into_report() noexcept { /* TODO */ } //TODO: Maybe just use `std::optional<std::reference_wrapper<Error>>`? Or Box<Report&>?
//constexpr virtual types::Option<Error&> inner() noexcept { return { std::nullopt }; }
constexpr virtual types::Option<Error const&> inner() const noexcept { return { std::nullopt }; }
constexpr virtual types::MaybeOwned<Report> into_report() & noexcept { /* TODO */ } //TODO: Maybe just use `std::optional<std::reference_wrapper<Error>>`? Or Box<Report&>?
constexpr virtual types::MaybeOwned<Report> into_report() && noexcept { /* TODO: auto{*this}.into_report() */ }
constexpr virtual ~Error() = default;
@ -47,7 +62,7 @@ namespace exopt {
};
// This is propagated returned, along with a stack trace. (base, abstract)
class TracedError : Error {
class TracedError : public Error {
public:
TracedError(TracedError const &) = default;
TracedError(TracedError &&) = default;
@ -62,13 +77,27 @@ namespace exopt {
std::source_location loc = std::source_location::current()
, std::stacktrace trace = std::stacktrace::current()) noexcept
: Error(std::move(loc)), m_trace(std::move(trace)) {}
/*inline TracedError(
std::stacktrace&& trace,
, std::source_location loc = std::source_location::current()) noexcept
: Error(std::move(loc)), m_trace(std::move(trace)) {}*/
inline TracedError(
std::stacktrace&& trace,
, std::source_location&& loc) noexcept
: Error(std::move(loc)), m_trace(std::move(trace)) {}
private:
inline types::Box<TracedError> into_traced(std::stacktrace trace&&) &&noexcept override final// = std::stacktrace::current()) && noexcept;
{ return typed::Box<TracedError> { std::move(*this) }; } //TODO: use `boxed::is_boxed_value()` to check if *this is boxed... Uhh, actually... XXX: No that doesn't work, since `Box<T>` doesn't make `T` visible to `boxed::ObjectBase`.... Eh...
inline typed::Box<TracedError> into_traced(std::stacktrace trace = std::stacktrace::current()) && noexcept override final
{ return typed::Box<TracedError> { std::move(*this) }; }
std::stacktrace m_trace; //TODO: Maybe box this? idk how big it is...
};
#if 0
// This is thrown. (virtual base, abstract)
class Panic : /*virtual?? I think it should be XXX*/ Error {
//TODO: How to make a `constexpr` deduced panic that has stack-trace at runtime only? Is it needed? Hnm...
};
//TODO: Fatal(string) : public virtual Panic
#endif
}

@ -22,36 +22,49 @@ extern "C" {
#define _exopt__internal __attribute__((visibility("internal")))
#define _exopt__readonly __attribute__((__pure__))
#define _exopt__pure __attribute__((__const__))
#ifdef DEBUG
# define _exopt__DEBUG 1
#else
# define _exopt__DEBUG 0
#endif
#ifdef RELEASE
# define _exopt__RELEASE 1
#else
# define _exopt__RELEASE 0
#endif
#ifdef _EO__OPT_OLD_ASSUME
# define _EO_ASSUME(X) ({ if(! (X)) __builtin_unreachable(); })
#endif
#define _EO_FIXED_INT(B) _BitInt(B)
#define _EO_GNU_ATTR(...) __attribute__((__VA_ARGS__))
#define _EO_NOTHING
#if _EO(cpp)
# define _EO_ATTR(...) [[__VA_ARGS__]]
# define _EO_SHARES_LAYOUT(T, U) (sizeof(T) == sizeof(U) && alignof(T) == alignof(U))
# ifndef _EO_ASSUME
# define _EO_ASSUME(X) [[gnu::assume(X)]]
# endif
# define _exopt__restrict __restrict__
#else /* C */
# define _EO_ATTR(...) __attribute__((__VA_ARGS__))
# define _EO_SHARES_LAYOUT(T, U) (sizeof(T) == sizeof(U) && alignof(T) == alignof(U))
# define _EO_SHARES_LAYOUT(T, U) (sizeof(T) == sizeof(U) && _Alignof(T) == _Alignof(U))
#define _EO_CTORS_BASIC_W(N, constexpr, noexcept, ...) \
constexpr N(N const&) noexcept __VA_ARGS__; \
constexpr N(N &&) noexcept __VA_ARGS__; \
constexpr N & operator=(N const&) noexcept __VA_ARGS__; \
constexpr N & operator=(N &&) noexcept __VA_ARGS__
#define _EO_CTORS_BASIC(...) _EO_CTORS_BASIC_W(__VA_ARGS__, _EO_NOTHING, _EO_NOTHING, _EO_NOTHING)
#define _EO_CTORS_BASIC_DEFAULT(...) _EO_CTORS_BASIC(__VA_ARGS__, _EO_NOTHING, _EO_NOTHING, = default)
#define _EO_CTORS_DEFAULT_BASIC_W(N, constexpr, noexcept, ...) \
constexpr N(N const&) noexcept = default; \
constexpr N(N &&) noexcept = default; \
constexpr N & operator=(N const&) noexcept = default; \
constexpr N & operator=(N &&) noexcept = default
#define _EO_CTORS_DEFAULT_BASIC(N) _EO_CTORS_DEFAULT_BASIC_W(N, constexpr, _EO_NOTHING, _EO_NOTHING)
#define _EO_CTORS_DEFAULT_BASIC_RT(N) _EO_CTORS_DEFAULT_BASIC_W(N, _EO_NOTHING, _EO_NOTHING, _EO_NOTHING)
#define _EO_CTORS_DEFAULT_BASIC_NE_W(N, C) _EO_CTORS_DEFAULT_BASIC_W(N, C, noexcept, _EO_NOTHING)
#define _EO_CTORS_DEFAULT_BASIC_NE(N) _EO_CTORS_DEFAULT_BASIC_W(N, constexpr, noexcept, _EO_NOTHING)
#define _EO_CTORS_DEFAULT_BASIC_NE_RT(N) _EO_CTORS_DEFAULT_BASIC_W(N, _EO_NOTHING, noexcept, _EO_NOTHING)
#define _EO_CLASS_DEFAULT_(N, ...) N
#define _EO_CLASS_DEFAULT_2(_, N, ...) N
#define _EO_CLASS_DEFAULT_3(_, __, N, ...) N
#define _EO_CLASS_DEFAULT_4(_, __, ___, N, ...) N
#define _EO_CLASS_DEFAULT_3REST(_, __, ___, ...) __VA_ARGS__
#define _EO_CLASS_DEFAULT_4REST(_, __, ___, ____, ...) __VA_ARGS__
// _EO_CLASS_DEFINE(NAME, [constexpr], [noexcept], [virtual], [=default])
#define _EO_CLASS_DEFINE(...) _EO_CTORS_BASIC(__VA_ARGS__); \
_EO_CLASS_DEFAULT_4(__VA_ARGS__, _EO_NOTHING, _EO_NOTHING, _EO_NOTHING, _EO_NOTHING, _EO_NOTHING) _EO_CLASS_DEFAULT_2(__VA_ARGS__, _EO_NOTHING, _EO_NOTHING) ~ _EO_CLASS_DEFAULT_(__VA_ARGS__, _EO_NOTHING)() _EO_CLASS_DEFAULT_3(__VA_ARGS__, _EO_NOTHING, _EO_NOTHING, _EO_NOTHING) _EO_CLASS_DEFAULT_4REST(__VA_ARGS__, _EO_NOTHING, _EO_NOTHING, _EO_NOTHING, _EO_NOTHING, _EO_NOTHING)
#else
# ifndef _EO_ASSUME
# define _EO_ASSUME(X) __attribute__((assume(X)))
# endif

@ -29,7 +29,9 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t<held_type> t) noexcept { return std::ref<T>(*t); }
constexpr static inline auto sentinel = nullptr;
};
};
template<typename T>
struct null_optimise<ptr::Unique<T> > { constexpr static inline bool value = true;
@ -218,6 +220,11 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
template<typename T>
using mut_ref_t = typename mut_ref_or_void<T>::type;
template<typename T>
using remove_ref_keep_const_t = std::conditional_t,
<std::is_const_v<std::remove_reference_v<T> >
,const std::remove_cvref_t<T>
, std::remove_cvref_t<T> >;
template<typename T> requires(requires(T v) { { v == nullptr } -> std::convertible_to<bool>; })
[[gnu::always_inline]]
@ -266,7 +273,27 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
template<AsOption T>
struct Option final {
class Option final {
using value_type_correct = _details::remove_ref_keep_const_t<T>;// std::remove_cvref_t<T>;
constexpr static inline bool is_const_v = std::is_const_v<value_type_correct>;
template<typename U, typename V = const std::remove_const_t<U> >
using correct_const_t = std::conditional_t<is_const_v, V, U>;
public:
[[gnu::const]]
constexpr bool is_mutable() const noexcept { return !is_const_v; }
using value_type = std::remove_const_t<value_type_correct>;
using pointer_type = correct_const_t<value_type*, value_type const*>;
using const_pointer_type = value_type const*;
using reference_type = correct_const_t<value_type*, value_type const&>;
using const_reference_type = value_type const&;
using move_reference_type = std::add_rvalue_reference_t<value_type_correct>;
using const_move_reference_type = value_type const&&;
struct UnwrapError : public OptionUnwrapError
{
UnwrapError() noexcept =default;
@ -285,7 +312,9 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
}
constexpr Option(std::optional<T>&& opt) noexcept requires(!std::is_void_v<T>)
: inner_( bool(opt) ? std::move(*opt) : nullptr ) {}
: Option(nullptr) {
if(opt) inner_ = decltype(inner_)(std::move(*opt));
}
constexpr explicit Option(std::nullptr_t) noexcept : inner_(nullptr) {}
constexpr Option(none_t) noexcept : Option(nullptr) {}
@ -298,9 +327,28 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
: 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>)
constexpr explicit(std::convertible_to<std::add_rvalue_reference_t<T>, Option<T>>)
Option(_details::rv_ref_t<T> value)
noexcept(std::is_nothrow_invocable_v<convert_to_held_rv, std::add_rvalue_reference_t<T>>)
requires(!std::is_void_v<T>)
: inner_(convert_to_held_rv(std::forward<decltype(value)>(value)))
{}
constexpr explicit Option(pointer_type const&& ptr) noexcept(std::is_nothrow_move_constructible_v<decltype(*ptr)>) requires(!std::is_void_v<T>)
Option(nullptr) {
if(ptr) inner_ = decltype(inner_)(convert_to_held_rv(std::move(*ptr)));
}
constexpr explicit Option(const_pointer_type ptr) noexcept(std::is_nothrow_copy_constructible_v<decltype(*ptr)>) requires(!std::is_void_v<T>)
: Option(nullptr) {
if(ptr) inner_ = decltype(inner_)(convert_to_held_rv(auto(*ptr)));
}
template<typename P> requires(!std::is_void_v<T> && (std::is_same_v<std::remove_cvref_t<P>, pointer_type>
or std::is_same_v<std::remove_cvref_t<P>, const_pointer_type>))
constexpr Option<T> from_pointer(P ptr) noexcept
{ if constexpr(std::is_const_v<decltype(*ptr)> or !std::is_rvalue_reference_v<P>)
return Option<T> { static_cast<const_pointer_type>(ptr) };
else return Option<T> { std::move(static_cast<pointer_type>(ptr)) }; }
//: inner_(ptr ? convert_to_held_rv(*ptr) : nullptr) {}
//TODO: copy/move assign where appropriate
constexpr Option& operator=(const Option& copy) noexcept(std::is_void_v<T> || std::is_nothrow_copy_constructible_v<decltype(copy.inner_)::held_type>)

@ -5,11 +5,6 @@
#include <concepts>
#include <bit>
#include <stdexcept>
#include <tuple>
#include "util.hh"
#define _EO_DETAILS namespace details [[gnu::visibility("internal")]]
namespace exopt::ptr {
template<typename T>
@ -29,13 +24,6 @@ namespace exopt::ptr {
template<typename T>
using AliasedRef = T __attribute__((__may_alias__))&;
using opaque_t = void;
using address_t = uintptr_t;
constexpr inline size_t address_bits_v = sizeof(address_t) * 8;
static_assert(sizeof(RawPtr<opaque_t>) == sizeof(address_t), "Bad address_t width");
template<typename T, size_t Size>
concept IsSize = sizeof(T) == Size;
@ -104,33 +92,6 @@ namespace exopt::ptr {
return std::forward<decltype(ptr)>(ptr);
}
template<typename E, typename... Args> requires(std::is_constructible_v<E, Args...>)
constexpr decltype(auto) throw_if_null(PointerEqCmp auto const& 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);
}
[[gnu::always_inline]]
constexpr decltype(auto) throw_if_null(PointerEqCmp auto const& ptr) {
if(__builtin_expect(ptr == nullptr, false)) throw_null_exception<decltype(ptr)>();
return std::forward<decltype(ptr)>(ptr);
}
// Throws NullDerefException<?> for first type of `ptr` that is null (if any are).
// If: `All == true`: Instead will always throw `NullDerefException<T...> (all types) if any are, and will not assume every pointer is likely to be non-null.
template<bool All = false>
[[gnu::always_inline]]
constexpr void throw_if_null(PointerEqCmp auto const&... ptr) requires(sizeof...(decltype(ptr)) > 1) {
if constexpr(All) {
using E = NullDerefException<decltype(ptr)...>;
((void)throw_if_null<E>(std::forward<decltype(ptr)>(ptr)),...);
} else ((void)throw_if_null(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)
{
@ -170,60 +131,17 @@ namespace exopt::ptr {
return *new T(std::forward<Args>(ctor)...);
}
template<typename... Types>
struct NullDerefException : public NullDerefException<> {
using types = std::tuple<Types...>;
using NullDerefException<>::NullDerefException;
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 {
/* constexpr auto value = util::concat_strings("pointer was null for types ", util::type_name<Types>()...);
return value;*/
return util::concat_str_v< std::string_view{"pointer was null for types "}, util::type_name<Types>()... >;
}
constexpr virtual ~NullDerefException() noexcept {}
};
template<>
struct NullDerefException<> /*TODO : public error::Error*/ {
constexpr NullDerefException() noexcept = default;
constexpr NullDerefException(const NullException&) noexcept = default;
constexpr NullDerefException& operator=(const NullException&) noexcept = default;
constexpr virtual ~NullDerefException() noexcept {}
constexpr virtual std::string_view message() const noexcept /*override*/ { return "pointer was null"; }
constexpr std::string_view message() const noexcept /*override*/ { return "pointer was null"; }
std::runtime_error as_runtime() &&noexcept; /*TODO: override*/
};
using NullException = NullDerefException<>;
template<typename T>
struct NullDerefException<T> : public NullDerefException<> {
using type = T;
using NullDerefException<>::NullDerefException;
constexpr std::string_view message() const noexcept override {
return util::concat_str_v< std::string_view{"pointer was null for type "}, util::type_name<type>() >;
}
constexpr virtual ~NullDerefException() noexcept {}
};
template<typename... Types>
consteval auto/*&&*/ null_exception() noexcept {
/*NullException&& value = NullDerefException<Types...>{};
return std::move(value); <- this isn't actually what we want I'm pretty sure...*/
return NullDerefException<Types...>{};
}
consteval NullException/*&&*/ null_exception() noexcept { return {}; }
template<typename... Types>
[[noreturn, gnu::noinline, gnu::cold]]
constexpr void throw_null_exception() { throw null_exception<Types...>(); }
[[noreturn, gnu::noinline, gnu::cold]]
constexpr void throw_null_exception() { throw null_exception(); }
template<typename T> requires(!std::is_reference_v<T>)
struct NonNull {
@ -412,20 +330,4 @@ namespace exopt::ptr {
}
static_assert(uniq_check(), "Invalid Unique<>.get()");
#endif
_EO_DETAILS {
[[gnu::const]]
address_t hash(const opaque_t*) noexcept;
}
/// Hash a pointer and retrieve a scrambled, unique value representing that memory address.
[[gnu::const]]
constexpr address_t hash(const opaque_t* ptr) noexcept {
if consteval {
throw "TODO: How to hash pointer in constexpr context?";
} else {
return details::hash(ptr);
}
}
}
#undef _EO_DETAILS

@ -68,12 +68,6 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
static_assert(_EO_CONSTANT_VALUE(std::string_view{"hello world"}) == std::string_view{"hello world"}, "Bad CONSTANT_VALUE()");
template<typename T, typename U>
concept shares_layout = sizeof(T) == sizeof(U) && alignof(T) == alignof(U);
template<typename T, typename U>
concept exact_type = std::is_same_v<T, U>;
template<typename T, size_t N, typename Array= std::array<T, N>, typename _I = std::make_index_sequence<N>>
constexpr auto array_literal(const T (&a)[N]) noexcept
-> Array
@ -95,201 +89,6 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
return _array_create(std::move(a), _I{});
}
template<size_t N, typename A = std::array<char, N>, size_t... Idx>
constexpr A substring_literal_as(const auto& str, std::index_sequence<Idx...>) noexcept
requires(requires(size_t n) {
{ str[n] } noexcept -> std::convertible_to<char>;
})
{
return { str[Idx]..., '\n' };
}
template<size_t... Idx>
constexpr auto substring_literal(const auto& str, std::index_sequence<Idx...>) noexcept
requires(std::is_invocable_v<substring_literal_as<sizeof...(Idx), std::array<char, sizeof...(Idx)>, Idx...>, decltype(str), std::index_sequence<Idx...>>)
{ return std::array{ str[Idx]..., '\n' }; }
template<typename T>
constexpr auto type_name_literal() noexcept
{
constexpr std::string_view prefix {
#if defined(__clang__)
"[T = "
#elif defined(__GNUC__)
"with T = "
#else
// Fuck MSVC, don't care.
#error Unsupported compiler
#endif
};
constexpr std::string_view suffix {"]"};
constexpr std::string_view function {__PRETTY_FUNCTION__};
constexpr auto start = function.find(prefix) + prefix.size();
constexpr auto end = function.rfind(suffix);
static_assert(start < end);
constexpr std::string_view name = function.substr(start, (end - start));
return substring_literal(name, std::make_index_sequence<name.size()>{});
}
template<typename T>
struct [[gnu::visibility("internal")]] type_name_of {
constexpr static inline auto value = type_name_literal<T>();
[[gnu::const]]
consteval operator std::string_view() const noexcept {
constexpr auto& v = value;
return std::string_view { v.data(), v.size() };
}
};
template<typename T>
constexpr auto type_name() noexcept -> std::string_view
{
constexpr auto& value = type_name_of<T>::value;
return std::string_view { value.data(), value.size() };
}
template<typename T>
constexpr inline auto type_name_v = type_name<T>();
template<typename S = std::string_view, S const&... Strs>
class [[gnu::visibility("internal")]] concat_str {
consteval static auto impl() noexcept
{
constexpr size_t len = (Strs.size() + ... + 0);
std::array<char, len+1> arr{};
auto append = [i=0, &arr](auto const& s) mutable {
for(auto c : s) arr[i++] = c;
};
(append(Strs), ...);
arr[len] = 0;
return arr;
}
public:
constexpr static inline auto literal = impl();
constexpr static inline S value { literal.data(), literal.size()-1 };
};
template<std::string_view const&... Ss>
constexpr static inline auto concat_str_v = concat_str<std::string_view, Ss...>::value;
template<typename S = std::string_view>
consteval S concat_strings(std::convertible_to<S> auto const&... strings) noexcept
{
return concat_str<S, S{strings}...>::value;
}
static_assert(concat_str_v< std::string_view{"hello"}, std::string_view{" "}, std::string_view{"view"} >
== std::string_view{"hello view"}, "concat_str_v<>: Concatenated string_view failed");
template<template<typename> P, typename... Values>
struct _EO(internal) is_all_match {
constexpr static inline auto value = (P<Values> && true && ...);
consteval operator auto() const noexcept { return value; }
};
template<template<typename> P, typename... Values>
struct _EO(internal) is_any_match {
constexpr static inline auto value = (P<Values> || false || ...);
consteval operator auto() const noexcept { return value; }
};
//XXX: Idk if template<template<s are allowed in this context...
template<template<typename> P, typename... Values> requires(requires{ typename is_all_match<P, Values>; })
constexpr inline auto is_all_match_v = is_all_match<P, Values...>::value;
template<template<typename> P, typename... Values> requires(requires{ typename is_any_match<P, Values>; })
constexpr inline auto is_any_match_v = is_any_match<P, Values...>::value;
template<auto... Values>
struct _EO(internal) is_all {
constexpr static inline auto value = (Values && true && ...);
};
template<auto... Values>
struct _EO(internal) is_any {
constexpr static inline auto value = (Values || false || ...);
};
template<auto... Values>
constexpr inline auto is_all_v = is_all<Values...>::value;
template<auto... Values>
constexpr inline auto is_any_v = is_any<Values...>::value;
template<typename... Args>
constexpr inline bool is_empty_v = sizeof...(Args) == 0;
template<auto... Values> // auto and typename aren't compatible unfortunately
constexpr inline bool is_empty_vv = sizeof...(Values) == 0;
// Convenience immediate function overloads for checking if a parameter pack *or* a value pack is empty without different names.
template<typename... A>
consteval bool is_empty_pack() noexcept { return sizeof...(A) == 0; }
template<auto... A>
consteval bool is_empty_pack() noexcept { return sizeof...(A) == 0; }
/// Deducable version of `is_empty_pack<typename...>()`
///
/// Can be used as `is_empty_pack(pack...)` instead of the slightly more ugly `is_empty_pack<decltype(pack)...>()` though with (very unlikely, but possible) implications of actually "evaluating" the pack (despite the values themselves are `const&` and never used, they're still passed.)
/// HOWEVER: The latter should likely be preferred, since this function takes possible-nonconstexpr types as arguments, it cannot be immediate like the non-argument taking overload. See below comment for more information on that.
template<typename... A>
[[gnu::const]] //TODO: XXX: This mostly useless function won't mess up overload resolution for `is_empty_pack<types...>()`, right? If it will, either find a way to make sure it comes LAST, or remove it, it's not worth the hassle just so users (i.e. me) can avoid typeing `decltype` for once in my fuckin life... what a stupud keyword name srsl (see static_assert() below.)
constexpr auto is_empty_pack(A const&...) noexcept { return is_empty_pack<A...>(); } // This overload exists for: e.g usage for index_sequence: \
`template<size_t N> f(const auto (&v)[N]) -> \
template<size_t Is...> _f(auto const& v, std::index_sequence<Is...>);`\
Can be used like: \
_f(const auto&v, std::convertible_to<size_t> auto&&... is) \
{ if constexpr(is_empty_pack(is...)) { ... } ... }` \
i.e: for unknown-typed non-type parameter packs `auto...`s packs can be `forward<decltype(pack)>(pack)...`'d (or just passed, as the taken value is always `const&`) to is_empty_pack(...) and `A...` can therefor be deduced. This is not possible for the non-value taking overloads, and also must be non-consteval since any `A` might not be constexpr. \
\
It is a convenience function that is essentially equivelent to `is_empty_pack<decltype(pack)...>()`, which should be preferred, since it is consteval and will always be guaranteed to be consteval regardless of the contents of `pack` itself, but I highly doubt any compiler will actually generate any argument-passing code for this overload either.
static_assert(!is_empty_pack<int, long, float>()
and is_empty_pack<>() //XXX: This might also be ambiguous between the type and value one... Eh.... That's actually useful though, so removing this explicit (and pointless) *usage* of the function from the test would be fine if they don't clash in other ways too.
and !is_empty_pack<0, 1, 2>()
and !is_empty_pack(0u, 1.0, 2l)
and is_empty_pack(), "`is_empty_pack()` overload issues.");
template<typename R, typename... Args>
constexpr auto map(auto const& fun, Args&&... values) noexcept(is_empty_v<Args...> or is_all_v<std::is_nothrow_invocable_v<decltype(fun), Args>...>)
requires(is_empty_v<Args...> or (is_all_v<std::is_invocable_v<decltype(fun), Args>...> and (
is_any_v<std::is_void_v<std::invoke_result_t<decltype(fun), Args>>...> ||
std::is_constructible_v<R, std::invoke_result_t<decltype(fun), Args>...>)))
-> std::conditional_t< is_empty_v<Args...> || is_any_v<std::is_void_v<std::invoke_result_t<decltype(fun), Args>>...>
, std::void_t<std::invoke_result_t<decltype(fun), Args>...>
, R>
{
if constexpr( is_any_v<std::is_void_v<std::invoke_result_t<decltype(fun), Args>>...> ) {
((void)std::invoke(fun, values),...);
else if constexpr(sizeof...(Args) == 0)
(void)0;
} else return { std::invoke(fun, values)... };
}
//XXX: To allow this, we might have to turn `map` from a function into a function-object class... Eh. \
Or we can just let the user specify `R` by using std::tuple as the lvalue assigned from the call maybe? Or we might have to change it to a `template<template<typename... Results>, typename... Args>` function-object class; but that would break the `void` case.... Eeeehh, can we partial-specialise a template<template into a template<typename? I don't think we can on the surface without helper ''trait'' templates.... Eh......
template<typename... Args>
using map_tuple = map<std::tuple<Args...>, Args...>;
//TODO: template< is_valid_metafunction_with_two_unknown_type_arguments_and_a_possibly_variable_return_type<R(T1, T2)> Op, typename... Args> \
auto reduce(auto const& fun, Op const& comb, Args&&... values) noexcept(...) \
requires(???) -> std::invoke_result_t<??????> { // TODO: Reframe the call to `comb` as an operator overload in a shim class, and use binary fold to make the call, like: ` \
shim s{comb}; return (s + std::invoke(fun, values) + ...); }`
template<typename Fn, typename... Args> requires((std::is_invocable_v<Fn, Args> && ...))
constexpr void apply(const Fn& fun, Args&&... values) noexcept((std::is_nothrow_invocable_v<Fn, Args> && ...))
/*-> std::conditional_t< (std::is_void_v<std::invoke_result_t<Fn, Args>> || ...)
, void
, std::common_type_t< std::invoke_result_t<Fn, Args>... > >*/
{ map<void, Args...>(fun, std::forward<Args>(values)...); }
struct [[gnu::visibility("internal")]] CTInternalFatalError {
constexpr CTInternalFatalError() noexcept = default;
constexpr CTInternalFatalError(CTInternalFatalError const&) noexcept = default;
@ -303,33 +102,29 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
consteval virtual std::string_view message() const noexcept =0;
};
/// Throw a string at runtime, as an `std::runtime_error`.
[[noreturn, gnu::noinline, gnu::cold]]
void throw_runtime(std::string_view&&);
/// Throw a string at runtime, or halt compilation with a `CTInternalFatalError`
[[noreturn]]//, gnu::noinline, gnu::cold]]
constexpr void throw_runtime(std::convertible_to<std::string_view> auto&& msg) {
std::string_view view{msg};
if consteval {
#define CTE CTInternalFatalError
using S = decltype(msg);
struct CTIFRuntimeError final : public CTE {
using CTE = CTInternalFatalError;
struct CTIFRuntimeError : public CTE {
using CTE::CTE;
consteval CTIFRuntimeError(S&& view) noexcept
consteval CTIFRuntimeError(std::string_view&& view) noexcept
: CTE(), m_message(std::move(view)) {}
consteval std::string_view message() const noexcept override { return { m_message }; }
consteval std::string_view message() const noexcept override { return m_message; }
constexpr virtual ~CTIFRuntimeError() noexcept {}
private:
S m_message;
std::string_view m_message;
};
#undef CTE
throw CTIFRuntimeError(std::move(msg));
throw CTIFRuntimeError(std::move(view));
} else {
static_cast<void (std::string_view&&)>(throw_runtime)({std::move(msg)}); //XXX: We may have to rename the above function (and make it [[internal]], or refactor to make it a private method of a function-object class `throw_runtime` to avoid ambiguations when constexpr-context calls it with the exact typed argument `std::string_view&&`... Maybe... I'm not really sure.
throw_runtime(std::move(view));
}
__builtin_unreachable();
}
// Returns an anonymous union with inavtive field: `T value` and active field `assume init` (no unique address).
template<typename T> requires(!std::is_reference_v<T>)
constexpr auto uninit() noexcept {

@ -0,0 +1,30 @@
#include <stdexcept>
#include <error.hh>
using namespace exopt::types;
namespace exopt {
Box<TracedError> Error::into_traced(std::stacktrace&& trace) &&noexcept {
struct DynamicTracedError final : TracedError {
_EO_CTORS_BASIC_DEFAULT(DynamicTracedError, _EO_NOTHING, noexcept);
virtual ~DynamicTracedError() noexcept = default;
DynamicTracedError(Error&& self, std::stacktrace&& trace) noexcept
: TracedError(std::move(trace), std::move(self.m_location))
, m_error(std::move(self)) {}
std::string_view message() const noexcept override { return m_error->message(); }
Option<Error const&> inner() const noexcept override { return Option<Error const&>(*m_error); }//{ return Option<Error const&>::from_pointer(std::as_const(m_error.get())); }
MaybeOwned<Report> into_report() && noexcept override { return std::move(*m_error).into_report(); }
MaybeOwned<Report> into_report() & noexcept override { return m_error->into_report(); }
private:
Box<Error> m_error;
};
#define ERR DynamicTracedError{std::move(*this), std::move(trace)}
return Box<TracedError>::template new_dynamic<DynamicTracedError, false>(ERR);
#undef ERR
}
}

@ -4,17 +4,6 @@
#include "pointer.h"
namespace exopt::ptr {
static_assert(address_bits_v == sizeof(address_t) * CHAR_BIT, "Bad CHAR_BIT: Must be 8");
std::runtime_error NullDerefException<>::as_runtime() &&noexcept { return std::runtime_error{std::string{message()} }; }
namespace details [[gnu::visibility("internal")]] {
[[gnu::const]]
address_t hash(const opaque_t* ptr) noexcept {
static_assert((address_bits_v & (64 | 32)) == address_bits_v, "Cannot hash pointer with address that is not 64 or 32 bits wide");
//TODO: Find good pointer hashing algorithm, or use crc64 (if constexpr(address_bits_v == 64), else crc32.)
}
}
std::runtime_error NullException::as_runtime() &&noexcept { return std::runtime_error{std::string{message()} }; }
}

Loading…
Cancel
Save