You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
libexopt/include/boxed.h

416 lines
16 KiB

#pragma once
#include <memory>
#include <utility>
#include "pointer.h"
#include "util.hh"
#include "exopt.h"
namespace exopt::types { namespace boxed {
template<typename T>
struct [[gnu::visibility("internal")]] boxable_value_type { using type = std::conditional_t
<std::is_reference_v<T>
,std::reference_wrapper<std::remove_cvref_t<T> >
,T>; };
namespace {
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>)
{ if(__builtin_expect(bool(c), true)) return std::make_unique<T>(*c); return nullptr; }
template<typename T>
constexpr std::unique_ptr<T> uniq_clone_unsafe(std::unique_ptr<T> const& c)
#ifndef DEBUG
noexcept(std::is_nothrow_copy_constructible_v<T>)
#endif
requires(std::is_copy_constructible_v<T>)
{ if(__builtin_expect(!bool(c), false))
#ifdef DEBUG
throw ptr::null_exception<T>{};
#else
__builtin_unreachable();
#endif
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>
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>;
};
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
{
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
/// 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 {
typedef boxable_value_type<T>::type type;
template<std::derived_from<T> U, bool RuntimeCheck=false>
constexpr static Box<T> new_dynamic(U&& v) noexcept(!RuntimeCheck) {
if constexpr(!RuntimeCheck) {
auto boxed = std::make_unique<U>(std::move(v));
return Box<T>{std::unique_ptr<T>{static_cast<T*>(boxed.release())}};
} else
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>)
: m_ptr{std::make_unique<T>()} {}
template<typename... Args> requires(std::is_constructible_v<T, Args...>)
constexpr Box(Args&&... ctor) noexcept(std::is_nothrow_constructible_v<T, Args...>)
: m_ptr{std::make_unique<T>(std::forward<Args>(ctor)...)} {}
constexpr Box(T&& value) noexcept(std::is_nothrow_constructible_v<T>)
: m_ptr{std::make_unique<T>(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<T>) requires(std::is_copy_constructible_v<T>)
: m_ptr{uniq_clone_unsafe(copy.m_ptr)} {}
constexpr Box& operator=(Box const& copy) noexcept(std::is_nothrow_copy_assignable_v<T>) requires(std::is_copy_assignable_v<T>)
{
if(__builtin_expect(this != std::addressof(copy), true)) {
m_ptr = uniq_clone_unsafe(copy.m_ptr);
} return *this;
}
[[gnu::nonnull(1)]]
constexpr static Box<T> from_raw_ptr(T* ptr) noexcept {
return Box<T>{ std::unique_ptr<T>{ ptr } };
}
constexpr static Box<T> from_raw_ptr(std::unique_ptr<T>&& uniq)
#if DEBUG
{ if(__builtin_expect(!uniq, false)) throw ptr::NullException{};
#else
noexcept {
_EO_ASSUME(uniq.get() != nullptr);
#endif
return Box<T>{ 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<T>&& into_unique() &&noexcept { return std::move(m_ptr); }
//TODO: Accessors, `(explicit?) operator std::unique_ptr<T> const&[&]`?, etc.
constexpr ~Box() = default;
private:
constexpr explicit Box(std::unique_ptr<T>&& 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<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;
};
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
template<typename Base, typename T = Base>
constexpr inline bool is_boxable_v = binary_convertible<_EO_ADD_RV<T>, _EO_ADD_RV<Base> > and requires(T&& value) {
typename Box<Base>;
typename Box<Base>::type;
{ std::make_unique<T>(std::move(value)) } -> std::same_as<std::unique_ptr<T>>;
};
template<typename T> concept is_boxable = is_boxable_v<T>;
template<typename T>
constexpr inline bool is_nothrow_boxable_v = is_boxable_v<T> && std::is_nothrow_constructible_v<Box<T>, T>;
#undef _EO_ADD_RV
template<typename T, typename U> requires(requires(T&& o) { static_cast<U&&>(o); })
constexpr Box<U> static_box_cast(Box<T>&& b) noexcept { return Box<U>::from_raw_ptr(static_cast<U*>(b.release())); }
template<typename T, typename U> requires(requires(T&& o) { static_cast<U&&>(o); })
constexpr Box<U> dynamic_box_cast(Box<T>&& b) {
auto* rel = b.release();
try {
return Box<U>::from_raw_ptr(std::addressof(dynamic_cast<U&&>(std::move(*rel))));
} catch(...) {
delete rel;
throw;
}
} //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>)
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)); }
template<typename 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)); }) {
return Box<T>::template new_dynamic<T>(std::move(value));
} else */return Box<T> { std::move(value) };
}
template<typename T>
constexpr Box<T> box(std::unique_ptr<T>&& value) noexcept { return Box<T> { 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;
}
namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
template<typename T>
struct null_optimise<boxed::Box<T> > { constexpr static inline bool value = true;
//XXX: Eh.. Idk if Option<T>'s lifetime design can make this work...
using held_type = boxed::Box<T>;
using type = boxed::Box<T>&;
constexpr static decltype(auto) convert_to_held(std::add_rvalue_reference_t<type> t) noexcept { return std::move(t); }
constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t<held_type> t) noexcept { return std::forward<type>(t); }
constexpr static inline T* sentinel = nullptr;
};
}