From 4698b48344e7ca0440709d3deae7ce1ffd0df546 Mon Sep 17 00:00:00 2001 From: Avril Date: Fri, 5 May 2023 04:31:48 +0100 Subject: [PATCH] Improving error interface: Begin working on `Box` to allow any `T` to tell if it is owned by a `Box`. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fortune for libexopt's current commit: Small blessing − 小吉 --- include/boxed.h | 126 ++++++++++++++++++++++++++++++++++++++++++++--- include/error.hh | 2 +- include/util.hh | 27 +++++++++- 3 files changed, 146 insertions(+), 9 deletions(-) diff --git a/include/boxed.h b/include/boxed.h index b1b3eaa..fd8fae7 100644 --- a/include/boxed.h +++ b/include/boxed.h @@ -10,12 +10,15 @@ #include "exopt.h" namespace exopt::types { namespace boxed { + template struct [[gnu::visibility("internal")]] boxable_value_type { using type = std::conditional_t ,std::reference_wrapper > ,T>; }; + namespace { + template constexpr std::unique_ptr uniq_clone(std::unique_ptr const& c) noexcept(std::is_nothrow_copy_constructible_v) requires(std::is_copy_constructible_v) { if(__builtin_expect(bool(c), true)) return std::make_unique(*c); return nullptr; } @@ -34,11 +37,74 @@ namespace exopt::types { namespace boxed { #endif return std::make_unique(*c); } + + template + constexpr decltype(auto) force_ok(auto&& mv) requires(std::is_convertible_v && std::is_default_constructible_v) + { + struct err { + [[gnu::noinline, gnu::cold, noreturn]] + static constexpr void _throw() { throw Error{}; } + }; + if( __builtin_expect(!bool(mv), false)) err::_throw(); + return std::forward(mv); + } + + } // _impl (anon) + + template concept binary_convertible = std::is_convertible_v and std::is_convertible_v; - + + template + concept polymorphic_castable = std::derived_from, std::decay_t> + or std::derived_from, std::decay_t> + or std::is_same_v, std::decay_t> + or requires(std::decay_t && from) { + { static_cast &&>(from) } -> std::convertible_to; + }; + + template requires(polymorphic_castable) + constexpr std::unique_ptr static_uniq_cast(std::unique_ptr&& from) noexcept + { + return std::unique_ptr { + static_cast( std::move(from).release() ) + }; + } + + template requires(polymorphic_castable) + constexpr std::unique_ptr dynamic_uniq_cast(std::unique_ptr&& from) noexcept + { + return std::unique_ptr { + dynamic_cast( std::move(from).release() ) + }; + } + + template requires(polymorphic_castable) + constexpr std::unique_ptr uniq_cast(std::unique_ptr&& 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(std::addressof(value))); + } template - struct Box { + constexpr bool is_boxed_type() noexcept { + using type = std::remove_reference_t; + constexpr type const* VALUE = nullptr; + return bool(dynamic_cast(VALUE)); + } + +// We need either: Tagged pointers (XOR, bit-mangled, manual heap, or GOT (external allocation) kind), \ + or (preferred): layout of Box to be `{ aligned(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 + struct Box final { typedef boxable_value_type::type type; template U, bool RuntimeCheck=false> @@ -50,6 +116,18 @@ namespace exopt::types { namespace boxed { return Box{std::make_unique(dynamic_cast(std::move(v)))}; } + template U, bool Check = false> + constexpr Box upcast() && noexcept(!Check) + { return Box { force_ok(uniq_cast(std::move(m_ptr))) }; } + + template requires(std::derived_from) + constexpr Box downcast() && noexcept(!Check) + { return Box { force_ok(uniq_cast(std::move(m_ptr))) }; } + + template U, bool Check = !std::derived_from > // 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 polycast() && noexcept(!Check) + { return Box { force_ok(uniq_cast(std::move(m_ptr))) }; } + constexpr Box() noexcept(std::is_nothrow_default_constructible_v) requires(std::is_default_constructible_v) : m_ptr{std::make_unique()} {} @@ -101,6 +179,17 @@ namespace exopt::types { namespace boxed { _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()}; + }; +#endif std::unique_ptr m_ptr; // Unique m_ptr; }; @@ -133,20 +222,43 @@ namespace exopt::types { namespace boxed { } } //TODO: Overload for `const&` that does the type check *before* the copy allocation. +#if 0 template requires(std::derived_from) - constexpr Box box(R&& value, util::comptime auto Check = _EO_CONSTANT_VALUE(false)) noexcept(std::is_nothrow_invocable_v< Box::new_dynamic) //XXX: This seems illegal... + constexpr Box box(R&& value, util::comptime auto Check = _EO_CONSTANT_VALUE(false)) noexcept(std::is_nothrow_invocable_v< Box::new_dynamic>) //XXX: This seems illegal... { return Box::template new_dynamic(std::move(value)); } template - constexpr Box box(std::convertible_to auto&& value) noexcept(std::is_nothrow_constructible_v, std::add_rvalue_reference_v>) + constexpr Box box(T&& value) noexcept(std::is_nothrow_constructible_v, T>) + { return { std::move(value) }; } +#endif + template + constexpr Box box(T&& value) noexcept(std::is_nothrow_constructible_v, decltype(value)>) { /*if constexpr(requires(decltype(value) v) { static_cast(std::move(v)); }) { return Box::template new_dynamic(std::move(value)); - } else */return { static_cast(std::move(value)) }; + } else */return Box { std::move(value) }; } + template - constexpr Box box(T&& value) noexcept(std::is_nothrow_constructible_v, T>) - { return { std::move(value) }; } + constexpr Box box(std::unique_ptr&& value) noexcept { return Box { std::move(value) }; } + + template /*requires(polymorphic_castable) + constexpr Box polybox(T&& value) noexcept + { + return Box{ std::move(value) }.polycast() + }*/ requires(std::derived_from) + constexpr Box polybox(T&& value) noexcept { + return Box{ std::move(value) }.polycast(); // Convertion from derived to base infallible, no need for check. + } + + template /*requires(polymorphic_castable) + constexpr Box polybox(T&& value) noexcept + { + return Box{ std::move(value) }.polycast() + }*/ requires(std::derived_from) + constexpr Box polybox(std::unique_ptr&& value) noexcept { + return Box{ std::move(value) }.polycast(); // Convertion from derived to base infallible, no need for check. + } } using boxed::Box; } diff --git a/include/error.hh b/include/error.hh index 8d7253a..d1d42d7 100644 --- a/include/error.hh +++ b/include/error.hh @@ -88,7 +88,7 @@ namespace exopt { private: inline types::Box into_traced(std::stacktrace trace&&) &&noexcept override final// = std::stacktrace::current()) && noexcept; - { return typed::Box { 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 { 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` doesn't make `T` visible to `boxed::ObjectBase`.... Eh... inline typed::Box into_traced(std::stacktrace trace = std::stacktrace::current()) && noexcept override final { return typed::Box { std::move(*this) }; } std::stacktrace m_trace; //TODO: Maybe box this? idk how big it is... diff --git a/include/util.hh b/include/util.hh index f043dc9..ead3315 100644 --- a/include/util.hh +++ b/include/util.hh @@ -12,6 +12,7 @@ #define _EO_CONSTANT_VALUE(X) ([]() { \ struct { \ typedef decltype(X) comptime_constant_t; \ + constexpr static inline auto VALUE = (X); \ consteval operator comptime_constant_t() const noexcept { return (X); } \ } inner; \ return inner; \ @@ -28,7 +29,7 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] { constexpr auto comptime_value(auto value) noexcept { using U = decltype(value); - struct inner { + struct inner final { typedef U comptime_constant_t; consteval operator comptime_constant_t() const noexcept { return std::move(value); } U value; @@ -38,7 +39,31 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] { }; return inner{ std::move(value) }; } + + template requires(comptime) + consteval T comptime_eval() noexcept { + if constexpr(requires { + { decltype(value)::VALUE } -> std::convertible_to; + }) { + return decltype(value)::VALUE; + } else return value; + + } + + template requires(comptime) + constexpr inline T comptime_eval_v = comptime_eval(); + + template + consteval T comptime_eval(comptime auto value) noexcept { + return comptime_eval(); + } + + + 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, "Bad comptime_eval() return type consteval"); + static_assert(std::is_same_v, "Bad comptime_eval() return type macro"); 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()");