Improving error interface: Begin working on `Box<T>` to allow any `T` to tell if it is owned by a `Box<T>`.

Fortune for libexopt's current commit: Small blessing − 小吉
error-handling
Avril 2 years ago
parent 2d30b3ce85
commit 4698b48344
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -10,12 +10,15 @@
#include "exopt.h" #include "exopt.h"
namespace exopt::types { namespace boxed { namespace exopt::types { namespace boxed {
template<typename T> template<typename T>
struct [[gnu::visibility("internal")]] boxable_value_type { using type = std::conditional_t struct [[gnu::visibility("internal")]] boxable_value_type { using type = std::conditional_t
<std::is_reference_v<T> <std::is_reference_v<T>
,std::reference_wrapper<std::remove_cvref_t<T> > ,std::reference_wrapper<std::remove_cvref_t<T> >
,T>; }; ,T>; };
namespace {
template<typename T> 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>) 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>)
{ if(__builtin_expect(bool(c), true)) return std::make_unique<T>(*c); return nullptr; } { if(__builtin_expect(bool(c), true)) return std::make_unique<T>(*c); return nullptr; }
@ -34,11 +37,74 @@ namespace exopt::types { namespace boxed {
#endif #endif
return std::make_unique<T>(*c); } return std::make_unique<T>(*c); }
template<typename Error = ptr::NullException>
constexpr decltype(auto) force_ok(auto&& mv) requires(std::is_convertible_v<decltype(mv), bool> && std::is_default_constructible_v<Error>)
{
struct err {
[[gnu::noinline, gnu::cold, noreturn]]
static constexpr void _throw() { throw Error{}; }
};
if( __builtin_expect(!bool(mv), false)) err::_throw();
return std::forward<decltype(mv)>(mv);
}
} // _impl (anon)
template<typename From, typename To> template<typename From, typename To>
concept binary_convertible = std::is_convertible_v<From, To> and std::is_convertible_v<To, From>; concept binary_convertible = std::is_convertible_v<From, To> and std::is_convertible_v<To, From>;
template<typename From, typename To>
concept polymorphic_castable = std::derived_from<std::decay_t<To>, std::decay_t<From>>
or std::derived_from<std::decay_t<From>, std::decay_t<To>>
or std::is_same_v<std::decay_t<From>, std::decay_t<To>>
or requires(std::decay_t<From> && from) {
{ static_cast<std::decay_t<To> &&>(from) } -> std::convertible_to<To>;
};
template<typename To, typename From> requires(polymorphic_castable<From, To>)
constexpr std::unique_ptr<To> static_uniq_cast(std::unique_ptr<From>&& from) noexcept
{
return std::unique_ptr<To> {
static_cast<To*>( std::move(from).release() )
};
}
template<typename To, typename From> requires(polymorphic_castable<From, To>)
constexpr std::unique_ptr<To> dynamic_uniq_cast(std::unique_ptr<From>&& from) noexcept
{
return std::unique_ptr<To> {
dynamic_cast<To*>( std::move(from).release() )
};
}
template<typename To, typename From, bool Check = false> requires(polymorphic_castable<From, To>)
constexpr std::unique_ptr<To> uniq_cast(std::unique_ptr<From>&& from) noexcept
{
if constexpr(Check) return dynamic_uniq_cast(std::move(from));
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> template<typename T>
struct Box { struct Box final {
typedef boxable_value_type<T>::type type; typedef boxable_value_type<T>::type type;
template<std::derived_from<T> U, bool RuntimeCheck=false> template<std::derived_from<T> U, bool RuntimeCheck=false>
@ -50,6 +116,18 @@ namespace exopt::types { namespace boxed {
return Box<T>{std::make_unique<T>(dynamic_cast<T&&>(std::move(v)))}; return Box<T>{std::make_unique<T>(dynamic_cast<T&&>(std::move(v)))};
} }
template<std::derived_from<T> U, bool Check = false>
constexpr Box<U> upcast() && noexcept(!Check)
{ return Box<U> { force_ok(uniq_cast<U, T, Check>(std::move(m_ptr))) }; }
template<typename U, bool Check = true> requires(std::derived_from<T, U>)
constexpr Box<U> downcast() && noexcept(!Check)
{ return Box<U> { force_ok(uniq_cast<U, T, Check>(std::move(m_ptr))) }; }
template<polymorphic_castable<T> U, bool Check = !std::derived_from<U, T> > // Default dynamic check set to false only if we can statically determine that T is derived from U and the conversion is therfore infallible
constexpr Box<U> polycast() && noexcept(!Check)
{ return Box<U> { force_ok(uniq_cast<U, T, Check>(std::move(m_ptr))) }; }
constexpr Box() noexcept(std::is_nothrow_default_constructible_v<T>) requires(std::is_default_constructible_v<T>) constexpr Box() noexcept(std::is_nothrow_default_constructible_v<T>) requires(std::is_default_constructible_v<T>)
: m_ptr{std::make_unique<T>()} {} : m_ptr{std::make_unique<T>()} {}
@ -101,6 +179,17 @@ namespace exopt::types { namespace boxed {
_EO_ASSUME(m_ptr.get()); _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; std::unique_ptr<T> m_ptr; // Unique<T> m_ptr;
}; };
@ -133,20 +222,43 @@ namespace exopt::types { namespace boxed {
} }
} //TODO: Overload for `const&` that does the type check *before* the copy allocation. } //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>) 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>)
constexpr Box<T> box(R&& value, util::comptime<bool> auto Check = _EO_CONSTANT_VALUE(false)) noexcept(std::is_nothrow_invocable_v< Box<T>::new_dynamic<R, bool(Check)>) //XXX: This seems illegal... constexpr Box<T> box(R&& value, util::comptime<bool> auto Check = _EO_CONSTANT_VALUE(false)) noexcept(std::is_nothrow_invocable_v< Box<T>::new_dynamic<R, util::comptime_eval_v<bool, Check>>) //XXX: This seems illegal...
{ return Box<T>::template new_dynamic<R, Check>(std::move(value)); } { return Box<T>::template new_dynamic<R, Check>(std::move(value)); }
template<typename T> template<typename T>
constexpr Box<T> box(std::convertible_to<T> auto&& value) noexcept(std::is_nothrow_constructible_v<Box<T>, std::add_rvalue_reference_v<T>>) constexpr Box<T> box(T&& value) noexcept(std::is_nothrow_constructible_v<Box<T>, T>)
{ return { std::move(value) }; }
#endif
template<typename T>
constexpr Box<T> box(T&& value) noexcept(std::is_nothrow_constructible_v<Box<T>, decltype(value)>)
{ {
/*if constexpr(requires(decltype(value) v) { static_cast<T&&>(std::move(v)); }) { /*if constexpr(requires(decltype(value) v) { static_cast<T&&>(std::move(v)); }) {
return Box<T>::template new_dynamic<T>(std::move(value)); return Box<T>::template new_dynamic<T>(std::move(value));
} else */return { static_cast<T>(std::move(value)) }; } else */return Box<T> { std::move(value) };
} }
template<typename T> template<typename T>
constexpr Box<T> box(T&& value) noexcept(std::is_nothrow_constructible_v<Box<T>, T>) constexpr Box<T> box(std::unique_ptr<T>&& value) noexcept { return Box<T> { std::move(value) }; }
{ return { std::move(value) }; }
template<typename Base, typename T> /*requires(polymorphic_castable<Base, T>)
constexpr Box<Base> polybox(T&& value) noexcept
{
return Box<T>{ std::move(value) }.polycast<Base>()
}*/ requires(std::derived_from<T, Base>)
constexpr Box<Base> polybox(T&& value) noexcept {
return Box<T>{ std::move(value) }.polycast<Base, false>(); // Convertion from derived to base infallible, no need for check.
}
template<typename Base, typename T> /*requires(polymorphic_castable<Base, T>)
constexpr Box<Base> polybox(T&& value) noexcept
{
return Box<T>{ std::move(value) }.polycast<Base>()
}*/ requires(std::derived_from<T, Base>)
constexpr Box<Base> polybox(std::unique_ptr<T>&& value) noexcept {
return Box<T>{ std::move(value) }.polycast<Base, false>(); // Convertion from derived to base infallible, no need for check.
}
} }
using boxed::Box; using boxed::Box;
} }

@ -88,7 +88,7 @@ namespace exopt {
private: private:
inline types::Box<TracedError> into_traced(std::stacktrace trace&&) &&noexcept override final// = std::stacktrace::current()) && noexcept; inline types::Box<TracedError> into_traced(std::stacktrace trace&&) &&noexcept override final// = std::stacktrace::current()) && noexcept;
{ return typed::Box<TracedError> { std::move(*this) }; } //TODO: XXX: How to check if `this` is *already* boxed? This dynamic double-boxing is inefficient, but also can probably not be checked unless relying on enabling RTTI by default { 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 inline typed::Box<TracedError> into_traced(std::stacktrace trace = std::stacktrace::current()) && noexcept override final
{ return typed::Box<TracedError> { std::move(*this) }; } { return typed::Box<TracedError> { std::move(*this) }; }
std::stacktrace m_trace; //TODO: Maybe box this? idk how big it is... std::stacktrace m_trace; //TODO: Maybe box this? idk how big it is...

@ -12,6 +12,7 @@
#define _EO_CONSTANT_VALUE(X) ([]() { \ #define _EO_CONSTANT_VALUE(X) ([]() { \
struct { \ struct { \
typedef decltype(X) comptime_constant_t; \ typedef decltype(X) comptime_constant_t; \
constexpr static inline auto VALUE = (X); \
consteval operator comptime_constant_t() const noexcept { return (X); } \ consteval operator comptime_constant_t() const noexcept { return (X); } \
} inner; \ } inner; \
return inner; \ return inner; \
@ -28,7 +29,7 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
constexpr auto comptime_value(auto value) noexcept { constexpr auto comptime_value(auto value) noexcept {
using U = decltype(value); using U = decltype(value);
struct inner { struct inner final {
typedef U comptime_constant_t; typedef U comptime_constant_t;
consteval operator comptime_constant_t() const noexcept { return std::move(value); } consteval operator comptime_constant_t() const noexcept { return std::move(value); }
U value; U value;
@ -39,6 +40,30 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
return inner{ std::move(value) }; return inner{ std::move(value) };
} }
template<typename T, auto value> requires(comptime<decltype(value), T>)
consteval T comptime_eval() noexcept {
if constexpr(requires {
{ decltype(value)::VALUE } -> std::convertible_to<decltype(value)::comptime_constant_t>;
}) {
return decltype(value)::VALUE;
} else return value;
}
template<typename T, auto value> requires(comptime<decltype(value), T>)
constexpr inline T comptime_eval_v = comptime_eval<T, value>();
template<typename T>
consteval T comptime_eval(comptime<T> auto value) noexcept {
return comptime_eval<T, value>();
}
static_assert(comptime_eval(comptime_value(5.0)) == 5.0, "Bad comptime_eval() consteval");
static_assert(comptime_eval(_EO_CONSTANT_VALUE(3.f)) == 3.f, "Bad comptime_eval() macro");
static_assert(std::is_same_v<decltype(comptime_eval(comptime_value(4.f))), float>, "Bad comptime_eval() return type consteval");
static_assert(std::is_same_v<decltype(comptime_eval(_EO_CONSTANT_VALUE(4.0))), double>, "Bad comptime_eval() return type macro");
static_assert(comptime_value(std::string_view{"hello"}.size()) == 5, "Bad comptime_value()"); static_assert(comptime_value(std::string_view{"hello"}.size()) == 5, "Bad comptime_value()");
static_assert(_EO_CONSTANT_VALUE(std::string_view{"hello world"}) == std::string_view{"hello world"}, "Bad CONSTANT_VALUE()"); static_assert(_EO_CONSTANT_VALUE(std::string_view{"hello world"}) == std::string_view{"hello world"}, "Bad CONSTANT_VALUE()");

Loading…
Cancel
Save