diff --git a/include/boxed.h b/include/boxed.h index fd8fae7..3f32cee 100644 --- a/include/boxed.h +++ b/include/boxed.h @@ -31,7 +31,7 @@ namespace exopt::types { namespace boxed { requires(std::is_copy_constructible_v) { if(__builtin_expect(!bool(c), false)) #ifdef DEBUG - throw ptr::NullException{}; + throw ptr::null_exception{}; #else __builtin_unreachable(); #endif @@ -63,6 +63,86 @@ namespace exopt::types { namespace boxed { { 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 { @@ -102,6 +182,53 @@ namespace exopt::types { namespace boxed { 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 { @@ -179,18 +306,27 @@ 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; + 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; - box_type_tag identifier{box_tag_of()}; }; -#endif - std::unique_ptr m_ptr; // Unique m_ptr; + + 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 diff --git a/include/pointer.h b/include/pointer.h index 1604c44..5498f8e 100644 --- a/include/pointer.h +++ b/include/pointer.h @@ -5,6 +5,11 @@ #include #include #include +#include + +#include "util.hh" + +#define _EO_DETAILS namespace details [[gnu::visibility("internal")]] namespace exopt::ptr { template @@ -24,6 +29,13 @@ namespace exopt::ptr { template 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) == sizeof(address_t), "Bad address_t width"); + template concept IsSize = sizeof(T) == Size; @@ -131,17 +143,53 @@ namespace exopt::ptr { return *new T(std::forward(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 {} + template + struct NullDerefException : public NullDerefException<> { + using types = std::tuple; + using NullDerefException<>::NullDerefException; - constexpr std::string_view message() const noexcept /*override*/ { return "pointer was null"; } + constexpr std::string_view message() const noexcept override { +/* constexpr auto value = util::concat_strings("pointer was null for types ", util::type_name()...); + return value;*/ + return util::concat_str_v< std::string_view{"pointer was null for types "}, util::type_name()... >; + } + 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"; } std::runtime_error as_runtime() &&noexcept; /*TODO: override*/ }; + using NullException = NullDerefException<>; + + template + struct NullDerefException : 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() >; + } + constexpr virtual ~NullDerefException() noexcept {} + }; + + template + consteval auto/*&&*/ null_exception() noexcept { + /*NullException&& value = NullDerefException{}; + return std::move(value); <- this isn't actually what we want I'm pretty sure...*/ + return NullDerefException{}; + } + + consteval NullException/*&&*/ null_exception() noexcept { return {}; } + template requires(!std::is_reference_v) struct NonNull { @@ -330,4 +378,20 @@ 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 diff --git a/include/util.hh b/include/util.hh index ead3315..4d91ed8 100644 --- a/include/util.hh +++ b/include/util.hh @@ -89,6 +89,121 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] { return _array_create(std::move(a), _I{}); } + template, size_t... Idx> + constexpr A substring_literal_as(const auto& str, std::index_sequence) noexcept + requires(requires(size_t n) { + { str[n] } noexcept -> std::convertible_to; + }) + { + return { str[Idx]..., '\n' }; + } + template + constexpr auto substring_literal(const auto& str, std::index_sequence) noexcept + requires(std::is_invocable_v, Idx...>, decltype(str), std::index_sequence>) + { return std::array{ str[Idx]..., '\n' }; } + + template + 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{}); + } + + template + struct [[gnu::visibility("internal")]] type_name_of { + constexpr static inline auto value = type_name_literal(); + + [[gnu::const]] + consteval operator std::string_view() const noexcept { + constexpr auto& v = value; + return std::string_view { v.data(), v.size() }; + } + }; + + template + constexpr auto type_name() noexcept -> std::string_view + { + constexpr auto& value = type_name_of::value; + return std::string_view { value.data(), value.size() }; + } + + template + constexpr inline auto type_name_v = type_name(); + + template + class [[gnu::visibility("internal")]] concat_str { + consteval static auto impl() noexcept + { + constexpr size_t len = (Strs.size() + ... + 0); + std::array 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 + constexpr static inline auto concat_str_v = concat_str::value; + + template + consteval S concat_strings(std::convertible_to auto const&... strings) noexcept + { + return concat_str::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 + constexpr auto map(auto const& fun, Args&&... values) noexcept((std::is_nothrow_invocable_v && ...)) + requires((std::is_invocable_v && ...) and ( + (std::is_void_v> || ...) || + std::is_constructible_v...>)) + -> std::conditional_t< (std::is_void_v> || ...) + , void + , R> + { + if constexpr( (std::is_void_v> || ...) ) { + ((void)std::invoke(fun, values),...); + } 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, typename... Args>` function-object class; but that would break the `void` case.... Eeeehh, can we partial-specialise a template