#pragma once #include #include #include "pointer.h" #include "util.hh" #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; } template constexpr std::unique_ptr uniq_clone_unsafe(std::unique_ptr const& c) #ifndef DEBUG noexcept(std::is_nothrow_copy_constructible_v) #endif requires(std::is_copy_constructible_v) { if(__builtin_expect(!bool(c), false)) #ifdef DEBUG throw ptr::null_exception{}; #else __builtin_unreachable(); #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; }; struct ErasedTypeDeleter { constexpr virtual ~ErasedTypeDeleter() = default; constexpr ErasedTypeDeleter() noexcept = default; constexpr virtual void apply_delete() = 0; //TODO: See below... }; template T> constexpr auto uniq_erase_type(std::unique_ptr&& from) noexcept { //TODO: Overload case for `unique_ptr` for `deleter` to apply (and contain) `D()` on back_cast()ed pointer T* instead of assuming default `delete`. 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* ptr; if constexpr(DOES_CHECK) { if(__builtin_expect(!(ptr = dynamic_cast(p)), false)) _throw(); } else ptr = static_cast(p); delete ptr;*/ delete static_cast(p); } constexpr T* back_cast(void *p) noexcept { return static_cast(p); } /*constexpr deleter() noexcept = default; constexpr ~deleter() noexcept = default;*/ }; return std::unique_ptr { //XXX: Cannot retain type information of most-derived class from this: \ dynamic_cast static_cast (std::move(from).release()) }; } template T, typename D> requires(requires{ typename std::unique_ptr; }) constexpr auto uniq_erase_type(std::unique_ptr&& from) noexcept { class deleter final { D m_del; public: constexpr void operator()(void* p) noexcept(std::is_nothrow_invocable_v) { m_del(static_cast(p)); } constexpr T* back_cast(void* p) noexcept { return static_cast(p); } constexpr deleter(D&& deleter) noexcept(std::is_nothrow_move_constructible_v) : m_del(std::move(deleter)) {} constexpr deleter() noexcept(std::is_nothrow_default_constructible_v) requires(std::is_default_constructible_v) =default;//: m_del() {} constexpr deleter(const deleter&) noexcept(std::is_nothrow_copy_constructible_v) = default; constexpr deleter(deleter&&) noexcept(std::is_nothrow_move_constructible_v) = default; constexpr deleter& operator=(const deleter&) noexcept(std::is_nothrow_copy_assignable_v) = default; constexpr deleter& operator=(deleter&&) noexcept(std::is_nothrow_move_assignable_v) = default; constexpr ~deleter() noexcept(std::is_nothrow_destructible_v) {} }; if constexpr(std::is_default_constructible_v) return std::unique_ptr { static_cast (std::move(from).release()), deleter() }; else { deleter&& del{std::move(from.get_deleter())}; return std::unique_ptr { static_cast (std::move(from).release()), deleter{std::move(del)} }; } } 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 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 /// Base class which is used to store the layout of Box's allocated memory. Intended for use to determine if a certain `T`'s `this` pointer is inside a `Box` or not. class dynamic_boxed_layout_base{ using Self = dynamic_boxed_layout_base; public: constexpr dynamic_boxed_layout_base() noexcept = default; constexpr ~dynamic_boxed_layout_base() = default; typedef void* (dynamic_cast_t)(Self*); typedef const void* (dynamic_const_cast_t)(Self const*); constexpr virtual size_t value_byte_offset() const noexcept =0; [[gnu::returns_nonnull]] constexpr void* extract_untyped_pointer() noexcept { return reinterpret_cast( reinterpret_cast(unsigned char*) + value_byte_offset() ); } [[gnu::returns_nonnull]] constexpr const void* extract_untyped_pointer() const noexcept { return reinterpret_cast( reinterpret_cast(const unsigned char*) + value_byte_offset() ); } constexpr virtual void* invoke_casting_shim(dynamic_cast_t const*) const noexcept =0; template constexpr bool is_typeof() const noexcept { return invoke_casting_shim([] (Self* self) { return dynamic_cast(self) template [[gnu::returns_nonnull]] inline T* extract_pointer_unsafe() noexcept { return reinterpret_cast(extract_untyped_pointer()); } template [[gnu::returns_nonnull]] inline const T* extract_pointer_unsafe() const noexcept { return reinterpret_cast(extract_untyped_pointer()); } template [[gnu::returns_nonnull]] inline T* try_extract_pointer() noexcept { return is_typeof() ? extract_pointer_unsafe() : nullptr; } }; template struct Box final { typedef boxable_value_type::type type; template U, bool RuntimeCheck=false> constexpr static Box new_dynamic(U&& v) noexcept(!RuntimeCheck) { if constexpr(!RuntimeCheck) { auto boxed = std::make_unique(std::move(v)); return Box{std::unique_ptr{static_cast(boxed.release())}}; } else 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()} {} template requires(std::is_constructible_v) constexpr Box(Args&&... ctor) noexcept(std::is_nothrow_constructible_v) : m_ptr{std::make_unique(std::forward(ctor)...)} {} constexpr Box(T&& value) noexcept(std::is_nothrow_constructible_v) : m_ptr{std::make_unique(std::move(value))} {} constexpr Box(Box&&) noexcept = default; constexpr Box& operator=(Box&&) noexcept = default; constexpr Box(const Box& copy) noexcept(std::is_nothrow_copy_constructible_v) requires(std::is_copy_constructible_v) : m_ptr{uniq_clone_unsafe(copy.m_ptr)} {} constexpr Box& operator=(Box const& copy) noexcept(std::is_nothrow_copy_assignable_v) requires(std::is_copy_assignable_v) { if(__builtin_expect(this != std::addressof(copy), true)) { m_ptr = uniq_clone_unsafe(copy.m_ptr); } return *this; } [[gnu::nonnull(1)]] constexpr static Box from_raw_ptr(T* ptr) noexcept { return Box{ std::unique_ptr{ ptr } }; } constexpr static Box from_raw_ptr(std::unique_ptr&& uniq) #if DEBUG { if(__builtin_expect(!uniq, false)) throw ptr::NullException{}; #else noexcept { _EO_ASSUME(uniq.get() != nullptr); #endif return Box{ std::move(uniq) }; } [[gnu::returns_nonnull]] constexpr T* get() const noexcept { return m_ptr.get(); } [[gnu::returns_nonnull]] constexpr T* release() noexcept { return m_ptr.release(); } constexpr std::unique_ptr&& into_unique() &&noexcept { return std::move(m_ptr); } //TODO: Accessors, `(explicit?) operator std::unique_ptr const&[&]`?, etc. constexpr ~Box() = default; private: constexpr explicit Box(std::unique_ptr&& ptr) : m_ptr(std::move(ptr)) { _EO_ASSUME(m_ptr.get()); } // TODO: Identifying if a value `this` pointer is boxed struct alignas(type) inner_layout_t final : public dynamic_boxed_layout_base { using boxed_value_t = type; constexpr inner_layout_t(type&& v) noexcept(std::is_nothrow_move_constructible_v) : value(std::move(v)) {} //TODO: All the ctors, assigns, and operators that can make this newtype wrapper more conveniently useable as a deref-target for accessing `value`. constexpr /*virtual*/ ~inner_layout_t() = default; [[gnu::const]] constexpr size_t value_byte_offset() const noexcept override { return offsetof(inner_layout_t, value); } type value; }; struct inner_unique_ptr_t final { std::unique_ptr }; inner_unique_ptr_t m_ptr; // Unique m_ptr; }; #define _EO_ADD_RV std::add_rvalue_reference_v template constexpr inline bool is_boxable_v = binary_convertible<_EO_ADD_RV, _EO_ADD_RV > and requires(T&& value) { typename Box; typename Box::type; { std::make_unique(std::move(value)) } -> std::same_as>; }; template concept is_boxable = is_boxable_v; template constexpr inline bool is_nothrow_boxable_v = is_boxable_v && std::is_nothrow_constructible_v, T>; #undef _EO_ADD_RV template requires(requires(T&& o) { static_cast(o); }) constexpr Box static_box_cast(Box&& b) noexcept { return Box::from_raw_ptr(static_cast(b.release())); } template requires(requires(T&& o) { static_cast(o); }) constexpr Box dynamic_box_cast(Box&& b) { auto* rel = b.release(); try { return Box::from_raw_ptr(std::addressof(dynamic_cast(std::move(*rel)))); } catch(...) { delete rel; throw; } } //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... { return Box::template new_dynamic(std::move(value)); } template 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 Box { std::move(value) }; } template 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; } namespace exopt::types { namespace optional [[gnu::visibility("internal")]] { template struct null_optimise > { constexpr static inline bool value = true; //XXX: Eh.. Idk if Option's lifetime design can make this work... using held_type = boxed::Box; using type = boxed::Box&; constexpr static decltype(auto) convert_to_held(std::add_rvalue_reference_t t) noexcept { return std::move(t); } constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t t) noexcept { return std::forward(t); } constexpr static inline T* sentinel = nullptr; }; }