Compare commits

..

1 Commits

@ -0,0 +1,123 @@
#pragma once
#include <array>
#include <utility>
#include <memory>
#include "pointer.h"
#include "util.hh"
#include "exopt.h"
namespace exopt::types {
namespace polyarray [[gnu::visibility("internal")]] {
template<typename T, size_t N>
struct SizedArray;
struct UnsizedAccessor {
constexpr virtual ~UnsizedAccessor() = default;
constexpr virtual T* data() const noexcept =0;
};
template<typename T, size_t N>
struct SizedAccessor : virtual UnsizedAccessor {
//using UnsizedAccessor::UnsizedAccessor;
constexpr virtual ~SizedAccessor() = default;
constexpr T* data() const noexcept override final { return raw()->data(); }
[[gnu::returns_nonnull]]
constexpr virtual std::array<T, N>* raw() noexcept =0;
[[gnu::returns_nonnull]]
constexpr virtual std::array<T, N> const* raw() const noexcept =0;
constexpr virtual std::array<T, N>& array() & noexcept { return *raw(); }
constexpr virtual std::array<T, N> const& array() const& noexcept { return *raw(); }
constexpr virtual std::array<T, N>&& array() && noexcept { return std::move(*raw()); }
constexpr virtual std::array<T, N> const&& array() const&& noexcept { return std::move(*raw()); }
};
/// An `std::array<T>` with unknown compile-time size.
template<typename T>
class PolyArray : public virtual UnsizedAccessor {
public:
constexpr PolyArray() noexcept = default;
constexpr virtual ~PolyArray() = default;
using UnsizedAccessor::data; //constexpr virtual T* data() const noexcept =0; Inherited from UnsizedAccessor
constexpr virtual size_t size() const noexcept =0;
//TODO: std::array-like accessors based on `data()..size()`
template<size_t N>
constexpr std::array<T, N>* try_downcast() noexcept {
if consteval {
static_assert(size() == N, "Invalid downcast array size");
}
if (size() == N) { return static_cast<SizedAccessor<T, N>*>(accessor())->raw(); } //reinterpret_cast<std::array<T, N>*>(unsafe_data()); }
return nullptr;
}
template<size_t N>
constexpr std::array<T, N>&& downcast() && {
if(auto* nar = try_downcast()) {
return std::move(*nar);
} else throw_invalid_size();
}
constexpr bool empty() const noexcept { return !size(); }
protected:
[[noreturn, gnu::noinline, gnu::cold]]
constexpr void throw_invalid_size() {
std::terminate(); //TODO: throw `poly_array_size_exception`
}
private:
[[gnu::returns_nonnull]]
constexpr UnsizedAccessor* accessor() noexcept { return static_cast<UnsizedAccessor*>(this); }
[[gnu::returns_nonnull]]
constexpr const UnsizedAccessor* accessor() const noexcept { return static_cast<UnsizedAccessor const*>(this); }
template<size_t N>
[[gnu::returns_nonnull]]
constexpr SizedAccessor<T, N>* unsafe_cast() noexcept { return static_cast<SizedAccessor<T, N>*>(
static_cast<UnsizedAccessor*>(this)); }
};
template<typename T, size_t N>
struct SizedArray : virtual /*XXX: Needed? since both non-virtual bases of this class have virtual base UnsizedAccessor?*/ SizedAccessor<T, N>
, public PolyArray<T> {
[[gnu::const]]
constexpr size_t size() const noexcept override final { return N; }
//[[gnu::const, gnu::returns_nonnull]] // Unneeded, SizedAccessor implements it as raw()->data()
//constexpr T* data() const noexcept override final { return m_data.data(); }
constexpr SizedArray(std::array<T, N>&& m) noexcept
: m_data(std::move(m)) {}
//TODO: ctors, assign, etc.
constexpr virtual ~SizedArray() = default;
//TODO: operator*() decltype(auto) forwards from SizedAccessor::array()
protected:
[[gnu::returns_nonnull]]
constexpr std::array<T, N>* raw() noexcept override final { return std::addressof(m_data); }
[[gnu::returns_nonnull]]
constexpr std::array<T, N> const* raw() const noexcept override final { return std::addressof(m_data); }
private:
std::array<T, N> m_data;
};
template<typename T, size_t N>
constexpr std::unique_ptr<PolyArray<T>> make_unsized(std::array<T, N>&& array) noexcept {
auto sub = std::make_unique<SizedArray<T, N>>(std::move(array));
return std::unique_ptr<PolyArray<T>> { static_cast<PolyArray<T>*>(sub.release()) };
}
template<typename T, size_t N>
constexpr std::array<T, N>&& /*XXX: <- Is returning an rvalue-ref here acceptible? When is `array` dropped here? Does the caller actuall drop it or the std::move() in here...? We aren't actually assigning the move to anything at all, we're just working through rvalue-refs, so it *should* be fine...??? Fuck this language honestly... */ make_sized(std::unique_ptr<PolyArray<T>>&& array) {
return (*std::move(array)).downcast<T, N>();
}
}
}

@ -10,13 +10,14 @@
#include "exopt.h" #include "exopt.h"
namespace exopt::types { namespace boxed { namespace exopt::types { namespace boxed {
template<typename T> template<typename T>
struct [[gnu::visibility("internal")]] boxable_value_type { using type = std::conditional_t struct [[gnu::visibility("internal")]] boxable_value_type { using type = std::conditional_t
<std::is_reference_v<T> <std::is_reference_v<T>
,std::reference_wrapper<std::remove_cvref_t<T> > ,std::reference_wrapper<std::remove_cvref_t<T> >
,T>; }; ,T>; };
namespace { namespace {
template<typename T> 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>) 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>)
@ -30,7 +31,7 @@ namespace exopt::types { namespace boxed {
requires(std::is_copy_constructible_v<T>) requires(std::is_copy_constructible_v<T>)
{ if(__builtin_expect(!bool(c), false)) { if(__builtin_expect(!bool(c), false))
#ifdef DEBUG #ifdef DEBUG
throw ptr::NullDerefException<T>{}; throw ptr::null_exception<T>{};
#else #else
__builtin_unreachable(); __builtin_unreachable();
#endif #endif
@ -62,16 +63,17 @@ namespace exopt::types { namespace boxed {
{ static_cast<std::decay_t<To> &&>(from) } -> std::convertible_to<To>; { static_cast<std::decay_t<To> &&>(from) } -> std::convertible_to<To>;
}; };
/*struct ErasedTypeDeleter { struct ErasedTypeDeleter {
constexpr virtual ~ErasedTypeDeleter() = default; constexpr virtual ~ErasedTypeDeleter() = default;
constexpr ErasedTypeDeleter() noexcept = default; constexpr ErasedTypeDeleter() noexcept = default;
constexpr virtual void apply_delete() = 0; constexpr virtual void apply_delete() = 0;
//TODO: See below... //TODO: See below...
};*/ };
template<polymorphic_castable<void> T> template<polymorphic_castable<void> T>
constexpr auto uniq_erase_type(std::unique_ptr<T>&& from) noexcept { 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?*/ { struct deleter final /*: public ErasedTypeDeleter XXX <- Is this useful, for unerasing the type, maybe?*/ {
typedef T type; typedef T type;
@ -92,26 +94,18 @@ namespace exopt::types { namespace boxed {
delete static_cast<T*>(p); delete static_cast<T*>(p);
} }
constexpr T* back_cast(void *p) noexcept { return static_cast<T*>(p); } constexpr T* back_cast(void *p) noexcept { return static_cast<T*>(p); }
constexpr deleter() noexcept = default; /*constexpr deleter() noexcept = default;
constexpr deleter(deleter const&) noexcept = default; constexpr ~deleter() noexcept = default;*/
constexpr deleter(deleter &&) noexcept = default;
constexpr deleter& operator=(deleter const&) noexcept = default;
constexpr deleter& operator=(deleter &&) noexcept = default;
constexpr ~deleter() noexcept = default;
}; };
return std::unique_ptr<void, deleter> { return std::unique_ptr<void, deleter> {
// Cannot retain type information of most-derived class from this: \ //XXX: Cannot retain type information of most-derived class from this: \
dynamic_cast<void*> dynamic_cast<void*>
static_cast<void*> static_cast<void*>
(std::move(from).release()) (std::move(from).release())
}; };
} }
template<polymorphic_castable<void> T, typename D> requires(requires{ template<polymorphic_castable<void> T, typename D> requires(requires{ typename std::unique_ptr<T, D>; })
typename std::unique_ptr<T, D>;
requires(!std::is_same_v<std::unique_ptr<T, D>::deleter_type, std::unique_ptr<T>::deleter_type);
})
constexpr auto uniq_erase_type(std::unique_ptr<T, D>&& from) noexcept { constexpr auto uniq_erase_type(std::unique_ptr<T, D>&& from) noexcept {
class deleter final { class deleter final {
D m_del; D m_del;
@ -133,7 +127,6 @@ namespace exopt::types { namespace boxed {
constexpr deleter& operator=(const deleter&) noexcept(std::is_nothrow_copy_assignable_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& operator=(deleter&&) noexcept(std::is_nothrow_move_assignable_v<D>) = default;
constexpr ~deleter() noexcept(std::is_nothrow_destructible_v<D>) requires(std::is_trivially_destructible_v<D>) = default;
constexpr ~deleter() noexcept(std::is_nothrow_destructible_v<D>) {} constexpr ~deleter() noexcept(std::is_nothrow_destructible_v<D>) {}
}; };
if constexpr(std::is_default_constructible_v<D>) if constexpr(std::is_default_constructible_v<D>)
@ -173,8 +166,72 @@ namespace exopt::types { namespace boxed {
else return static_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> template<typename T>
struct alignas(std::unique_ptr<T>) Box { struct Box final {
typedef boxable_value_type<T>::type type; typedef boxable_value_type<T>::type type;
template<std::derived_from<T> U, bool RuntimeCheck=false> template<std::derived_from<T> U, bool RuntimeCheck=false>
@ -220,24 +277,15 @@ namespace exopt::types { namespace boxed {
} }
[[gnu::nonnull(1)]] [[gnu::nonnull(1)]]
constexpr static Box<T> from_raw_ptr(T* ptr) noexcept(!_EO(DEBUG)) { constexpr static Box<T> from_raw_ptr(T* ptr) noexcept {
#if DEBUG
ptr::throw_if_null(ptr);
#endif
return Box<T>{ std::unique_ptr<T>{ ptr } }; return Box<T>{ std::unique_ptr<T>{ ptr } };
} }
constexpr static Box<T> from_raw_ptr(std::unique_ptr<T>&& uniq)
constexpr static Box<T> from_raw_ptr(std::unique_ptr<T>&& uniq) {
if(__builtin_expect(!bool(uniq), false)) ptr::throw_null_exception<T>();
_EO_ASSUME(uniq.get() != nullptr);
return Box<T> { std::move(uniq) };
}
constexpr static Box<T> from_raw_ptr_unchecked(std::unique_ptr<T>&& uniq) noexcept(!_EO(DEBUG)) {
#if DEBUG #if DEBUG
if(__builtin_expect(!uniq, false)) ptr::throw_null_exception<T>(); { if(__builtin_expect(!uniq, false)) throw ptr::NullException{};
#else #else
noexcept {
_EO_ASSUME(uniq.get() != nullptr); _EO_ASSUME(uniq.get() != nullptr);
#endif #endif
return Box<T>{ std::move(uniq) }; return Box<T>{ std::move(uniq) };
@ -252,63 +300,49 @@ namespace exopt::types { namespace boxed {
//TODO: Accessors, `(explicit?) operator std::unique_ptr<T> const&[&]`?, etc. //TODO: Accessors, `(explicit?) operator std::unique_ptr<T> const&[&]`?, etc.
constexpr ~Box() = default; constexpr ~Box() = default;
protected: private:
// For types that may need to invalidate the Box<T> guarantee without exposing it in API, they can inherit (non-virtual). constexpr explicit Box(std::unique_ptr<T>&& ptr)
: m_ptr(std::move(ptr)) {
// For unsafe operations, wether or not to apply checks. _EO_ASSUME(m_ptr.get());
enum unsafe_t {
UNSAFE,
};
enum maybe_unsafe_t {
CHECKED,
DEBUG_CHECKED,
};
constexpr Box(util::exact_type<unsafe_t> auto u, std::unique_ptr<T>&& ptr) noexcept
: m_ptr(std::move(ptr)) { (void)u; }
constexpr static Box<T> from_raw_ptr(unsafe_t u, std::unique_ptr<T>&& uniq) noexcept {
return Box{u, std::move(uniq)};
} }
constexpr std::unique_ptr<T>& as_unique(unsafe_t) & noexcept { return m_ptr; } // TODO: Identifying if a value `this` pointer is boxed
constexpr std::unique_ptr<T> const & as_unique(unsafe_t) const& noexcept { return m_ptr; } struct alignas(type) inner_layout_t final : public dynamic_boxed_layout_base {
using boxed_value_t = type;
constexpr std::unique_ptr<T>&& as_unique(unsafe_t) && noexcept { return std::move(m_ptr); } constexpr inner_layout_t(type&& v) noexcept(std::is_nothrow_move_constructible_v<type>)
constexpr std::unique_ptr<T> const&& as_unique(unsafe_t) const&& noexcept { return std::move(m_ptr); } : 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;
constexpr T* as_unsafe_ptr(unsafe_t) const noexcept { return m_ptr.get(); } [[gnu::const]]
constexpr std::unique_ptr<T> swap_unsafe_ptr(unsafe_t u, T* raw) noexcept constexpr size_t value_byte_offset() const noexcept override { return offsetof(inner_layout_t, value); }
{ return (void)u; std::exchange(std::move(m_ptr), std::unique_ptr<T>{ raw }); }
constexpr void set_unsafe_ptr(unsafe_t 8, T* raw) noexcept {
(void)u;
m_ptr.reset(raw);
}
constexpr decltype(auto) set_unsafe(unsafe_t u, std::unique_ptr<T>&& ptr) noexcept
{ (void)u; return std::exchange(std::move(m_ptr), std::move(ptr)); }
constexpr auto& swap_unsafe(unsafe_t, std::unique_ptr<T>& ptr) noexcept {
using std::swap;
swap(m_ptr, ptr);
return m_ptr;
}
constexpr T* release_unsafe(unsafe_t) noexcept { return m_ptr.release(); } type value;
private: };
constexpr explicit Box(std::unique_ptr<T>&& ptr) noexcept
: m_ptr(std::move(ptr)) { struct inner_unique_ptr_t final {
_EO_ASSUME(m_ptr.get()); std::unique_ptr<inner_layout_t>
} };
std::unique_ptr<T> m_ptr; // Unique<T> m_ptr; inner_unique_ptr_t m_ptr; // Unique<T> m_ptr;
}; };
static_assert(util::shares_layout<Box<_Complex double>, _Complex double*>, "Invalid Box<T> memory layout: More than just T*");
static_assert(util::shares_layout<Box<_Complex double>, std::unique_ptr<_Complex double>>, "Invalid Box<T> memory layout: More than just std::unique_ptr<T>");
#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> concept is_boxable = is_boxable_v<T>;
template<typename T> template<typename T>
constexpr inline bool is_nothrow_boxable_v = is_boxable_v<T> && std::is_nothrow_constructible_v<Box<T>, 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); }) 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())); } constexpr Box<U> static_box_cast(Box<T>&& b) noexcept { return Box<U>::from_raw_ptr(static_cast<U*>(b.release())); }
@ -322,12 +356,7 @@ namespace exopt::types { namespace boxed {
delete rel; delete rel;
throw; throw;
} }
} } //TODO: Overload for `const&` that does the type check *before* the copy allocation.
template<typename T, typename U> requires(requires(T const& o) { static_cast<U const&>(o); })
constexpr Box<U> dynamic_box_cast(Box<T> const& b) {
return Box<U>::from_raw_ptr(std::addressof(dynamic_cast<U const&>(*b)));
}
#if 0 #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>) 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>)
@ -370,8 +399,6 @@ namespace exopt::types { namespace boxed {
using boxed::Box; using boxed::Box;
} }
//TODO: See phone notes on correctly implementing the nullopt for Box<T>
#if 0
namespace exopt::types { namespace optional [[gnu::visibility("internal")]] { namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
template<typename T> template<typename T>
struct null_optimise<boxed::Box<T> > { constexpr static inline bool value = true; struct null_optimise<boxed::Box<T> > { constexpr static inline bool value = true;
@ -386,4 +413,3 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
} }
#endif

@ -22,36 +22,17 @@ extern "C" {
#define _exopt__internal __attribute__((visibility("internal"))) #define _exopt__internal __attribute__((visibility("internal")))
#define _exopt__readonly __attribute__((__pure__)) #define _exopt__readonly __attribute__((__pure__))
#define _exopt__pure __attribute__((__const__)) #define _exopt__pure __attribute__((__const__))
#ifdef DEBUG
# define _exopt__DEBUG 1
#else
# define _exopt__DEBUG 0
#endif
#ifdef RELEASE
# define _exopt__RELEASE 1
#else
# define _exopt__RELEASE 0
#endif
#ifdef _EO__OPT_OLD_ASSUME #ifdef _EO__OPT_OLD_ASSUME
# define _EO_ASSUME(X) ({ if(! (X)) __builtin_unreachable(); }) # define _EO_ASSUME(X) ({ if(! (X)) __builtin_unreachable(); })
#endif #endif
#define _EO_FIXED_INT(B) _BitInt(B)
#define _EO_GNU_ATTR(...) __attribute__((__VA_ARGS__))
#if _EO(cpp) #if _EO(cpp)
# define _EO_ATTR(...) [[__VA_ARGS__]]
# define _EO_SHARES_LAYOUT(T, U) (sizeof(T) == sizeof(U) && alignof(T) == alignof(U))
# ifndef _EO_ASSUME # ifndef _EO_ASSUME
# define _EO_ASSUME(X) [[gnu::assume(X)]] # define _EO_ASSUME(X) [[gnu::assume(X)]]
# endif # endif
# define _exopt__restrict __restrict__ # define _exopt__restrict __restrict__
#else /* C */ #else
# define _EO_ATTR(...) __attribute__((__VA_ARGS__))
# define _EO_SHARES_LAYOUT(T, U) (sizeof(T) == sizeof(U) && alignof(T) == alignof(U))
# define _EO_SHARES_LAYOUT(T, U) (sizeof(T) == sizeof(U) && _Alignof(T) == _Alignof(U))
# ifndef _EO_ASSUME # ifndef _EO_ASSUME
# define _EO_ASSUME(X) __attribute__((assume(X))) # define _EO_ASSUME(X) __attribute__((assume(X)))
# endif # endif

@ -29,7 +29,9 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t<held_type> t) noexcept { return std::ref<T>(*t); } constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t<held_type> t) noexcept { return std::ref<T>(*t); }
constexpr static inline auto sentinel = nullptr; constexpr static inline auto sentinel = nullptr;
}; };
template<typename T> template<typename T>
struct null_optimise<ptr::Unique<T> > { constexpr static inline bool value = true; struct null_optimise<ptr::Unique<T> > { constexpr static inline bool value = true;

@ -104,33 +104,6 @@ namespace exopt::ptr {
return std::forward<decltype(ptr)>(ptr); return std::forward<decltype(ptr)>(ptr);
} }
template<typename E, typename... Args> requires(std::is_constructible_v<E, Args...>)
constexpr decltype(auto) throw_if_null(PointerEqCmp auto const& ptr, Args&&... error) {
auto _throw = [&] [[noreturn, gnu::noinline, gnu::cold]] () {
throw E{std::forward<Args>(error)...};
};
if(ptr == nullptr) _throw();
return std::forward<decltype(ptr)>(ptr);
}
[[gnu::always_inline]]
constexpr decltype(auto) throw_if_null(PointerEqCmp auto const& ptr) {
if(__builtin_expect(ptr == nullptr, false)) throw_null_exception<decltype(ptr)>();
return std::forward<decltype(ptr)>(ptr);
}
// Throws NullDerefException<?> for first type of `ptr` that is null (if any are).
// If: `All == true`: Instead will always throw `NullDerefException<T...> (all types) if any are, and will not assume every pointer is likely to be non-null.
template<bool All = false>
[[gnu::always_inline]]
constexpr void throw_if_null(PointerEqCmp auto const&... ptr) requires(sizeof...(decltype(ptr)) > 1) {
if constexpr(All) {
using E = NullDerefException<decltype(ptr)...>;
((void)throw_if_null<E>(std::forward<decltype(ptr)>(ptr)),...);
} else ((void)throw_if_null(std::forward<decltype(ptr)>(ptr)),...);
}
template<typename E, typename... Args> requires(std::is_constructible_v<E, Args...>) template<typename E, typename... Args> requires(std::is_constructible_v<E, Args...>)
constexpr decltype(auto) deref_or_throw(PointerDeref auto&& ptr, Args&&... error) constexpr decltype(auto) deref_or_throw(PointerDeref auto&& ptr, Args&&... error)
{ {
@ -217,13 +190,6 @@ namespace exopt::ptr {
consteval NullException/*&&*/ null_exception() noexcept { return {}; } consteval NullException/*&&*/ null_exception() noexcept { return {}; }
template<typename... Types>
[[noreturn, gnu::noinline, gnu::cold]]
constexpr void throw_null_exception() { throw null_exception<Types...>(); }
[[noreturn, gnu::noinline, gnu::cold]]
constexpr void throw_null_exception() { throw null_exception(); }
template<typename T> requires(!std::is_reference_v<T>) template<typename T> requires(!std::is_reference_v<T>)
struct NonNull { struct NonNull {

@ -0,0 +1,53 @@
#pragma once
#include <bit>
#include <array>
#include <cstdint>
#include "util.hh"
#include "exopt.h"
namespace exopt::rng {
[[gnu::visibility("internal")]]
constexpr inline auto COMPILED_ENTROPY{
#if __has_include("compiled_entropy.dat")
#embed "compiled_entropy.dat"
#else
nullptr
#endif
};
constexpr inline auto has_compiled_entropy_v = !std::is_same_v<std::nullptr_t, decltype(COMPILED_ENTROPY)>;
// XXX: The usage of __COUNTER__ *might* work as wanted if we wrap it in _EO_CONSTANT_VALUE() and take `util::comptime<uint64_t> auto = _EO_CONSTANT_VALUE(__COUNTER__)` as default argument instead. Maybe... I don't know..
template<typename T = unsigned char>
consteval decltype(auto) comptime_rand_next() noexcept {
std::array<unsigned char, sizeof(T)> v;
for(int i=0;i<v.size();i++) v[i] = comptime_rand_next<unsigned char>();
return util::comptime_value(std::bit_cast<T>(std::move(v)));
}
template<>
consteval decltype(auto) comptime_rand_next<unsigned char>(uint64_t counter = __COUNTER__) noexcept {
return _EO_CONSTANT_VALUE(COMPILED_ENTROPY[counter % sizeof(COMPILED_ENTROPY)]);
}
consteval uint64_t seed_linear(uint64_t counter = __COUNTER__) noexcept { return uint64_t(counter); } //XXX: This is kinda a dumb experiment, the __TIME__ TU-specific seed should be fine, but __COUNTER__ is a token evaluated regardless of calling context, so... Unless it's used before the #include<>, it'll always be the same.
consteval uint64_t seed_translation() noexcept { return uint64_t(__TIME__); }
// Seed: __TIME__ or __COUNTER__
[[gnu::const]]
constexpr uint64_t splitmix64_once(uint64_t x) noexcept {
//TODO: implement splitmix64 here for constexpr/consteval contexts
}
constexpr inline uint64_t translation_unit_fixed_seed = splitmit64_once(seed_translation());
constexpr inline uint64_t linear_fixed_seed = splitmit64_once(seed_linear()); // ODR dependant?? When is __COUNTER__ evaluated? In preprocessing, right? So, before any compilation?
constexpr inline auto translation_unit_rolling_seed = ([] () {
struct {
constexpr operator uint64_t() const noexcept { return splitmix64_once(translation_unit_fixed_seed ^ seed_linear()) ^ _EO_CONSTANT_VALUE(splitmix64_once(__COUNTER__)); }
} inner;
return inner;
})();
}

@ -68,12 +68,6 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
static_assert(_EO_CONSTANT_VALUE(std::string_view{"hello world"}) == std::string_view{"hello world"}, "Bad CONSTANT_VALUE()"); static_assert(_EO_CONSTANT_VALUE(std::string_view{"hello world"}) == std::string_view{"hello world"}, "Bad CONSTANT_VALUE()");
template<typename T, typename U>
concept shares_layout = sizeof(T) == sizeof(U) && alignof(T) == alignof(U);
template<typename T, typename U>
concept exact_type = std::is_same_v<T, U>;
template<typename T, size_t N, typename Array= std::array<T, N>, typename _I = std::make_index_sequence<N>> template<typename T, size_t N, typename Array= std::array<T, N>, typename _I = std::make_index_sequence<N>>
constexpr auto array_literal(const T (&a)[N]) noexcept constexpr auto array_literal(const T (&a)[N]) noexcept
-> Array -> Array
@ -184,90 +178,17 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
static_assert(concat_str_v< std::string_view{"hello"}, std::string_view{" "}, std::string_view{"view"} > 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"); == std::string_view{"hello view"}, "concat_str_v<>: Concatenated string_view failed");
template<template<typename> P, typename... Values>
struct _EO(internal) is_all_match {
constexpr static inline auto value = (P<Values> && true && ...);
consteval operator auto() const noexcept { return value; }
};
template<template<typename> P, typename... Values>
struct _EO(internal) is_any_match {
constexpr static inline auto value = (P<Values> || false || ...);
consteval operator auto() const noexcept { return value; }
};
//XXX: Idk if template<template<s are allowed in this context...
template<template<typename> P, typename... Values> requires(requires{ typename is_all_match<P, Values>; })
constexpr inline auto is_all_match_v = is_all_match<P, Values...>::value;
template<template<typename> P, typename... Values> requires(requires{ typename is_any_match<P, Values>; })
constexpr inline auto is_any_match_v = is_any_match<P, Values...>::value;
template<auto... Values>
struct _EO(internal) is_all {
constexpr static inline auto value = (Values && true && ...);
};
template<auto... Values>
struct _EO(internal) is_any {
constexpr static inline auto value = (Values || false || ...);
};
template<auto... Values>
constexpr inline auto is_all_v = is_all<Values...>::value;
template<auto... Values>
constexpr inline auto is_any_v = is_any<Values...>::value;
template<typename... Args>
constexpr inline bool is_empty_v = sizeof...(Args) == 0;
template<auto... Values> // auto and typename aren't compatible unfortunately
constexpr inline bool is_empty_vv = sizeof...(Values) == 0;
// Convenience immediate function overloads for checking if a parameter pack *or* a value pack is empty without different names.
template<typename... A>
consteval bool is_empty_pack() noexcept { return sizeof...(A) == 0; }
template<auto... A>
consteval bool is_empty_pack() noexcept { return sizeof...(A) == 0; }
/// Deducable version of `is_empty_pack<typename...>()`
///
/// Can be used as `is_empty_pack(pack...)` instead of the slightly more ugly `is_empty_pack<decltype(pack)...>()` though with (very unlikely, but possible) implications of actually "evaluating" the pack (despite the values themselves are `const&` and never used, they're still passed.)
/// HOWEVER: The latter should likely be preferred, since this function takes possible-nonconstexpr types as arguments, it cannot be immediate like the non-argument taking overload. See below comment for more information on that.
template<typename... A>
[[gnu::const]] //TODO: XXX: This mostly useless function won't mess up overload resolution for `is_empty_pack<types...>()`, right? If it will, either find a way to make sure it comes LAST, or remove it, it's not worth the hassle just so users (i.e. me) can avoid typeing `decltype` for once in my fuckin life... what a stupud keyword name srsl (see static_assert() below.)
constexpr auto is_empty_pack(A const&...) noexcept { return is_empty_pack<A...>(); } // This overload exists for: e.g usage for index_sequence: \
`template<size_t N> f(const auto (&v)[N]) -> \
template<size_t Is...> _f(auto const& v, std::index_sequence<Is...>);`\
Can be used like: \
_f(const auto&v, std::convertible_to<size_t> auto&&... is) \
{ if constexpr(is_empty_pack(is...)) { ... } ... }` \
i.e: for unknown-typed non-type parameter packs `auto...`s packs can be `forward<decltype(pack)>(pack)...`'d (or just passed, as the taken value is always `const&`) to is_empty_pack(...) and `A...` can therefor be deduced. This is not possible for the non-value taking overloads, and also must be non-consteval since any `A` might not be constexpr. \
\
It is a convenience function that is essentially equivelent to `is_empty_pack<decltype(pack)...>()`, which should be preferred, since it is consteval and will always be guaranteed to be consteval regardless of the contents of `pack` itself, but I highly doubt any compiler will actually generate any argument-passing code for this overload either.
static_assert(!is_empty_pack<int, long, float>()
and is_empty_pack<>() //XXX: This might also be ambiguous between the type and value one... Eh.... That's actually useful though, so removing this explicit (and pointless) *usage* of the function from the test would be fine if they don't clash in other ways too.
and !is_empty_pack<0, 1, 2>()
and !is_empty_pack(0u, 1.0, 2l)
and is_empty_pack(), "`is_empty_pack()` overload issues.");
template<typename R, typename... Args> template<typename R, typename... Args>
constexpr auto map(auto const& fun, Args&&... values) noexcept(is_empty_v<Args...> or is_all_v<std::is_nothrow_invocable_v<decltype(fun), Args>...>) constexpr auto map(auto const& fun, Args&&... values) noexcept((std::is_nothrow_invocable_v<decltype(fun), Args> && ...))
requires(is_empty_v<Args...> or (is_all_v<std::is_invocable_v<decltype(fun), Args>...> and ( requires((std::is_invocable_v<decltype(fun), Args> && ...) and (
is_any_v<std::is_void_v<std::invoke_result_t<decltype(fun), Args>>...> || (std::is_void_v<std::invoke_result_t<decltype(fun), Args>> || ...) ||
std::is_constructible_v<R, std::invoke_result_t<decltype(fun), Args>...>))) std::is_constructible_v<R, std::invoke_result_t<decltype(fun), Args>...>))
-> std::conditional_t< is_empty_v<Args...> || is_any_v<std::is_void_v<std::invoke_result_t<decltype(fun), Args>>...> -> std::conditional_t< (std::is_void_v<std::invoke_result_t<decltype(fun), Args>> || ...)
, std::void_t<std::invoke_result_t<decltype(fun), Args>...> , void
, R> , R>
{ {
if constexpr( is_any_v<std::is_void_v<std::invoke_result_t<decltype(fun), Args>>...> ) { if constexpr( (std::is_void_v<std::invoke_result_t<decltype(fun), Args>> || ...) ) {
((void)std::invoke(fun, values),...); ((void)std::invoke(fun, values),...);
else if constexpr(sizeof...(Args) == 0)
(void)0;
} else return { std::invoke(fun, values)... }; } else return { std::invoke(fun, values)... };
} }
@ -276,13 +197,6 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
template<typename... Args> template<typename... Args>
using map_tuple = map<std::tuple<Args...>, Args...>; using map_tuple = map<std::tuple<Args...>, Args...>;
//TODO: template< is_valid_metafunction_with_two_unknown_type_arguments_and_a_possibly_variable_return_type<R(T1, T2)> Op, typename... Args> \
auto reduce(auto const& fun, Op const& comb, Args&&... values) noexcept(...) \
requires(???) -> std::invoke_result_t<??????> { // TODO: Reframe the call to `comb` as an operator overload in a shim class, and use binary fold to make the call, like: ` \
shim s{comb}; return (s + std::invoke(fun, values) + ...); }`
template<typename Fn, typename... Args> requires((std::is_invocable_v<Fn, 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> && ...)) 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>> || ...) /*-> std::conditional_t< (std::is_void_v<std::invoke_result_t<Fn, Args>> || ...)

Loading…
Cancel
Save