Changed `NullException` to add optional type information. Added constexpr util::type_of<T>(), added constexpr `std::string_view` concatenation: `util::concat_str_v<std::string_view...>`, added `std::string_view util::concat_strings(auto const&...)`.

(Pulled from dropped `boxed_is_boxed_value`.)

Fortune for libexopt's current commit: Small curse − 小凶
master
Avril 2 years ago
parent a7e4a3ffcc
commit 3bdd503cb2
Signed by: flanchan
GPG Key ID: 284488987C31F630

@ -31,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::NullException{};
throw ptr::null_exception<T>{};
#else
__builtin_unreachable();
#endif
@ -63,6 +63,86 @@ 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 {
//TODO: Overload case for `unique_ptr<T, D>` 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>) {
/* 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() noexcept = default;*/
};
return std::unique_ptr<void, deleter> {
//XXX: 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>; })
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>) {}
};
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
{
@ -102,6 +182,53 @@ namespace exopt::types { namespace boxed {
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
/// Base class which is used to store the layout of Box<T>'s allocated memory. Intended for use to determine if a certain `T`'s `this` pointer is inside a `Box<T>` 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<void*>(
reinterpret_cast<this>(unsigned char*) + value_byte_offset()
);
}
[[gnu::returns_nonnull]]
constexpr const void* extract_untyped_pointer() const noexcept {
return reinterpret_cast<void const*>(
reinterpret_cast<this>(const unsigned char*) + value_byte_offset()
);
}
constexpr virtual void* invoke_casting_shim(dynamic_cast_t const*) const noexcept =0;
template<typename T>
constexpr bool is_typeof() const noexcept { return invoke_casting_shim([] (Self* self) { return dynamic_cast<T*>(self)
template<typename T>
[[gnu::returns_nonnull]]
inline T* extract_pointer_unsafe() noexcept {
return reinterpret_cast<T*>(extract_untyped_pointer());
}
template<typename T>
[[gnu::returns_nonnull]]
inline const T* extract_pointer_unsafe() const noexcept {
return reinterpret_cast<const T*>(extract_untyped_pointer());
}
template<typename T>
[[gnu::returns_nonnull]]
inline T* try_extract_pointer() noexcept {
return is_typeof<T>() ? extract_pointer_unsafe<T>() : nullptr;
}
};
template<typename T>
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<type>)
: 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<T>()};
};
#endif
std::unique_ptr<T> m_ptr; // Unique<T> m_ptr;
struct inner_unique_ptr_t final {
std::unique_ptr<inner_layout_t>
};
inner_unique_ptr_t m_ptr; // Unique<T> m_ptr;
};
#define _EO_ADD_RV std::add_rvalue_reference_v

@ -5,6 +5,11 @@
#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>
@ -24,6 +29,13 @@ 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;
@ -131,17 +143,53 @@ namespace exopt::ptr {
return *new T(std::forward<Args>(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<typename... Types>
struct NullDerefException : public NullDerefException<> {
using types = std::tuple<Types...>;
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<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"; }
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 T> requires(!std::is_reference_v<T>)
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

@ -89,6 +89,121 @@ 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<typename R, typename... Args>
constexpr auto map(auto const& fun, Args&&... values) noexcept((std::is_nothrow_invocable_v<decltype(fun), Args> && ...))
requires((std::is_invocable_v<decltype(fun), Args> && ...) and (
(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< (std::is_void_v<std::invoke_result_t<decltype(fun), Args>> || ...)
, void
, R>
{
if constexpr( (std::is_void_v<std::invoke_result_t<decltype(fun), Args>> || ...) ) {
((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<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...>;
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;
@ -102,29 +217,33 @@ 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 {
using CTE = CTInternalFatalError;
struct CTIFRuntimeError : public CTE {
#define CTE CTInternalFatalError
using S = decltype(msg);
struct CTIFRuntimeError final : public CTE {
using CTE::CTE;
consteval CTIFRuntimeError(std::string_view&& view) noexcept
consteval CTIFRuntimeError(S&& 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:
std::string_view m_message;
S m_message;
};
throw CTIFRuntimeError(std::move(view));
#undef CTE
throw CTIFRuntimeError(std::move(msg));
} else {
throw_runtime(std::move(view));
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.
}
__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 {

@ -4,6 +4,17 @@
#include "pointer.h"
namespace exopt::ptr {
std::runtime_error NullException::as_runtime() &&noexcept { return std::runtime_error{std::string{message()} }; }
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.)
}
}
}

Loading…
Cancel
Save