Compare commits

...

2 Commits

@ -10,14 +10,13 @@
#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 {
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>)
@ -31,7 +30,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::NullDerefException<T>{};
#else
__builtin_unreachable();
#endif
@ -63,6 +62,94 @@ 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 {
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(deleter const&) 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> {
// 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>;
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 {
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>) requires(std::is_trivially_destructible_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
{
@ -86,25 +173,8 @@ namespace exopt::types { namespace boxed {
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
template<typename T>
struct Box final {
struct alignas(std::unique_ptr<T>) Box {
typedef boxable_value_type<T>::type type;
template<std::derived_from<T> U, bool RuntimeCheck=false>
@ -150,15 +220,24 @@ namespace exopt::types { namespace boxed {
}
[[gnu::nonnull(1)]]
constexpr static Box<T> from_raw_ptr(T* ptr) noexcept {
constexpr static Box<T> from_raw_ptr(T* ptr) noexcept(!_EO(DEBUG)) {
#if DEBUG
ptr::throw_if_null(ptr);
#endif
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(__builtin_expect(!uniq, false)) throw ptr::NullException{};
if(__builtin_expect(!uniq, false)) ptr::throw_null_exception<T>();
#else
noexcept {
_EO_ASSUME(uniq.get() != nullptr);
#endif
return Box<T>{ std::move(uniq) };
@ -173,40 +252,63 @@ namespace exopt::types { namespace boxed {
//TODO: Accessors, `(explicit?) operator std::unique_ptr<T> const&[&]`?, etc.
constexpr ~Box() = default;
protected:
// For types that may need to invalidate the Box<T> guarantee without exposing it in API, they can inherit (non-virtual).
// For unsafe operations, wether or not to apply checks.
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; }
constexpr std::unique_ptr<T> const & as_unique(unsafe_t) const& noexcept { return m_ptr; }
constexpr std::unique_ptr<T>&& as_unique(unsafe_t) && noexcept { return std::move(m_ptr); }
constexpr std::unique_ptr<T> const&& as_unique(unsafe_t) const&& noexcept { return std::move(m_ptr); }
constexpr T* as_unsafe_ptr(unsafe_t) const noexcept { return m_ptr.get(); }
constexpr std::unique_ptr<T> swap_unsafe_ptr(unsafe_t u, T* raw) noexcept
{ 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(); }
private:
constexpr explicit Box(std::unique_ptr<T>&& ptr)
constexpr explicit Box(std::unique_ptr<T>&& ptr) noexcept
: m_ptr(std::move(ptr)) {
_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;
type value;
box_type_tag identifier{box_tag_of<T>()};
};
#endif
std::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>
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())); }
@ -220,7 +322,12 @@ namespace exopt::types { namespace boxed {
delete rel;
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
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>)
@ -263,6 +370,8 @@ namespace exopt::types { namespace boxed {
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")]] {
template<typename T>
struct null_optimise<boxed::Box<T> > { constexpr static inline bool value = true;
@ -277,3 +386,4 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
}
#endif

@ -22,17 +22,36 @@ extern "C" {
#define _exopt__internal __attribute__((visibility("internal")))
#define _exopt__readonly __attribute__((__pure__))
#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
# define _EO_ASSUME(X) ({ if(! (X)) __builtin_unreachable(); })
#endif
#define _EO_FIXED_INT(B) _BitInt(B)
#define _EO_GNU_ATTR(...) __attribute__((__VA_ARGS__))
#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
# define _EO_ASSUME(X) [[gnu::assume(X)]]
# endif
# define _exopt__restrict __restrict__
#else
#else /* C */
# 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
# define _EO_ASSUME(X) __attribute__((assume(X)))
# endif

@ -29,9 +29,7 @@ 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 inline auto sentinel = nullptr;
};
};
template<typename T>
struct null_optimise<ptr::Unique<T> > { constexpr static inline bool value = true;

@ -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;
@ -92,6 +104,33 @@ namespace exopt::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...>)
constexpr decltype(auto) deref_or_throw(PointerDeref auto&& ptr, Args&&... error)
{
@ -131,17 +170,60 @@ 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... 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>)
struct NonNull {
@ -330,4 +412,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

@ -68,6 +68,12 @@ 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()");
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>>
constexpr auto array_literal(const T (&a)[N]) noexcept
-> Array
@ -89,6 +95,201 @@ 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<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>
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>...>)
requires(is_empty_v<Args...> or (is_all_v<std::is_invocable_v<decltype(fun), Args>...> and (
is_any_v<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< is_empty_v<Args...> || is_any_v<std::is_void_v<std::invoke_result_t<decltype(fun), Args>>...>
, std::void_t<std::invoke_result_t<decltype(fun), Args>...>
, R>
{
if constexpr( is_any_v<std::is_void_v<std::invoke_result_t<decltype(fun), Args>>...> ) {
((void)std::invoke(fun, values),...);
else if constexpr(sizeof...(Args) == 0)
(void)0;
} 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...>;
//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> && ...))
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 +303,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