Compare commits

...

10 Commits

Author SHA1 Message Date
Avril e78c765f45
Added better null checks and assumptions. removed `is_boxed()` meta-polymorphism. added Box<T> children (non-virtual) to perform unsafe operations that may invalidate Box<T> invariant.
1 year ago
Avril 3bdd503cb2
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&...)`.
1 year ago
Avril a7e4a3ffcc
Merge dynamic boxing improvements from error-handling in preperation for `boxed::is_boxed_value(T*) noexcept -> bool` implementation considerations.
1 year ago
Avril b80da442b7
Redid formatting for `leven_diff()` prototype attributes.
1 year ago
Avril 1f92834108
Started rework of Error/Report/Panic interface.
1 year ago
Avril d4e45000bd
Started `util::maybe_uninit<T>`.
1 year ago
Avril 399ba10c25
Started `boxed::Box<T>`.
1 year ago
Avril ee3893907c
Added `ptr::Unique<T>`: `NonNull<T>` that cannot be aliased.
1 year ago
Avril d4c494c6c1
Fixed spooky "confused" compiler fatal error (w/ no error messages) in `make_ordered_strign()`.
1 year ago
Avril 7447615b48
Improved `NonNull` checking and added better mapping / pointer casting behaviour
1 year ago

2
.gitignore vendored

@ -1,4 +1,4 @@
obj/
lib*.so
lib*.so*
lib*.a
*.o

@ -0,0 +1,389 @@
#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::NullDerefException<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 {
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
{
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));
}
template<typename T>
struct alignas(std::unique_ptr<T>) Box {
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(!_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) {
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)) ptr::throw_null_exception<T>();
#else
_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;
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) noexcept
: m_ptr(std::move(ptr)) {
_EO_ASSUME(m_ptr.get());
}
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>");
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>;
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;
}
}
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>)
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;
}
//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;
//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;
};
}
#endif

@ -1,16 +1,62 @@
#pragma once
namespace exopt::types {
#if 0
//XXX: Eh, this trait design is NOT GOOD. Figure out a better one
namespace traits {
template<typename T>
struct impl_clone {
typedef T type;
constexpr static T clone(T const& v) const noexcept(std::is_nothrow_copy_constructible_v<type>) { return v; }
};
template<>
struct impl_clone<void> {};
template<typename> struct clone;
template<>
struct clone : impl_clone<void> {};
template<typename T>
struct clone : impl_clone<std::conditional_t< std::is_copy_constructible_v<T>, T, void> > {};
template<typename T>
struct clone : impl_clone<std::conditional_t
< requires(requires(T const& self) {
{ self.clone() } noexcept(std::is_nothrow_copy_constructible_v<T>)
-> std::same_as<T>;
})
, T
, void
> > {
constexpr static T clone(T const&
};
}
template<typename T>
concept Clone = requires(T const& self) {
typename traits::clone<T>::type;
{ traits::clone<T>::clone(self) } noexcept(std::is_nothrow_copy_constructible_v<T>) -> std::same_as<T>;
} or requires(T const& self) {
{ self.clone() } noexcept(std::is_nothrow_copy_constructible_v<T>) -> std::same_as<T>;
};
#endif
namespace either [[gnu::visibility("internal")]] {
//TODO: A version with std::shared_ptr<T> instead
template<typename T>//, typename P = T*>
class Cow final {
class Cow {
using var_t = std::variant<pointer_type // Borrowed
, value_type>; // Owned
public:
//TODO: Use pointer traits of P to deduce `pointer_type`, so we can have P be a shared_ptr too. Or perhaps, a specialisation for `Cow<std::shared_ptr<T>>` would be better
using value_type = std::remove_cvref_t<T>;
constexpr static inline bool Writeable = std::is_copy_constructible_v<value_type>;
constexpr static inline bool NTWriteable = std::is_nothrow_copy_constructible_v<value_type>;
using reference_type = value_type &;
using const_reference_type = value_type const&;
using move_reference_type = value_type &&;
@ -38,7 +84,6 @@ namespace exopt::types {
return { *pref };
else return { nullptr };
}
//TODO: into_owned(){, const}, to_owned(){, const}{&, &&}, etc..
//TODO: Operator overloads for access to `value_type`: operator*, operator->, etc.
constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v<value_type>) {
@ -59,46 +104,106 @@ namespace exopt::types {
constexpr ~Cow() noexcept(std::is_nothrow_destructible_v<value_type>) = default;
constexpr move_reference_type as_ref() && {
std::visit([](auto&& arg) -> move_reference_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type)
return std::move(arg);
else return std::move(*arg);
}, m_value);
return make_owned();
}
constexpr reference_type as_ref() & {
std::visit([](auto& arg) -> reference_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type)
return std::forward<decltype(arg)>(arg);
else return *arg;
}, m_value);
return make_owned();
}
constexpr const_reference_type as_ref() const {
std::visit([](const auto& arg) -> const_reference_type {
using U = std::remove_cvref_t<decltype(arg)>;
return std::visit([](const auto& arg) -> const_reference_type {
using U = std::remove_cvref_t<decltype(arg)>>;
if constexpr(std::is_same_v<U, value_type)
return std::forward<decltype(arg)>(arg);
else return *arg;
}, m_value);
}
//TODO: ^ is_empty() null-checks for `*arg`.
//TODO: ^ UNLIKELY(is_empty()) null-checks for `*arg`.
constexpr reference_type operator*() & noexcept {
return as_ref();
}
constexpr move_reference_type operator*() && noexcept {
return as_ref();
}
constexpr const_reference_type operator*() const noexcept {
return as_ref();
}
constexpr const_pointer_type get() const noexcept {
std::visit([](const auto& arg) -> const_pointer_type {
constexpr pointer_type operator->() noexcept {
return get();
}
constexpr const_pointer_type operator->() noexcept {
return get();
}
constexpr const_pointer_type get() const noexcept {
return std::visit([](const auto& arg) -> const_pointer_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type)
if constexpr(std::is_same_v<U, value_type>)
return std::addressof(arg);
else return arg;
}, m_value);
}
constexpr pointer_type get() noexcept {
std::visit([](auto& arg) -> pointer_type {
constexpr pointer_type get() noexcept {
return std::addressof(make_owned());
/*
return std::visit([](auto& arg) -> pointer_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type)
if constexpr(std::is_same_v<U, value_type>)
return std::addressof(arg);
else return arg;
}, m_value);
}, m_value);*/
}
constexpr reference_type make_owned() & noexcept(NTWriteable) requires(Writeable) {
if(const auto* ct_from = std::visit([&m_value](auto& arg) -> auto* {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, pointer_type>) return arg;
else return nullptr;
//if constexpr(std::is_same_v<U, value_type>) return false;
//else if(auto * refp = std::get_if<pointer_type>(m_value))
}, m_value)) return m_value.emplace<value_type>(*ct_from); //XXX: Do we need to create a temporary here for creating invariants by reading the old invariant?
}
constexpr move_reference_type make_owned() && noexcept(NTWriteable) requires(Writeable) {
if(const auto* ct_from = std::visit([&m_value](auto&& arg) -> auto* {
using U = std::remove_cvref_t<decltype(arg)>;
//TODO: I don't think this works the way I want it to... We'll need to emplace inside this function I think, since the moved temporary has been moved. It might not matter though.
if constexpr(std::is_same_v<U, pointer_type>) return arg;
else return nullptr;
//if constexpr(std::is_same_v<U, value_type>) return false;
//else if(auto * refp = std::get_if<pointer_type>(m_value))
}, std::move(m_value))) return std::move(m_value.emplace<value_type>(*ct_from)); //XXX: Do we need to create a temporary here for creating invariants by reading the old invariant?
}
//These commented out visits modify the pointee of the borrowed invariant. THIS IS NOT WHAT WE WANT. Instead, we emplace a copy and then copy it again (possibly elided: &) or return it as an rvalue reference (elided: &&)
constexpr value_type into_owned() & noexcept(NTWriteable) requires(Writeable) {
return make_owned();
/*return std::visit([](auto&& arg) -> move_reference_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type>)
return std::move(arg);
else return std::move(*arg);
}, std::move(m_value));*/
}
constexpr move_reference_type into_owned() && noexcept(NTWriteable) requires(Writeable) {
return make_owned();
/*return std::move(std::visit([](auto&& arg) -> move_reference_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type>)
return std::move(arg);
else return std::move(*arg);
}, std::move(m_value)));*/
}
protected:
constexpr bool is_empty() const noexcept {
@ -113,8 +218,11 @@ namespace exopt::types {
var_t m_value;
};
//template<typename T>
//TODO: We want it to work: class Cow<std::unique_ptr<T>> : public Cow<T> {};
#if 0
template<typename T>
class Cow<std::shared_ptr<T>> final {
class Cow<std::shared_ptr<T>> {
using var_t = std::variant<
std::weak_ptr<value_type> // Borrowed
, std::shared_ptr<value_type>>; // Owned
@ -133,12 +241,22 @@ namespace exopt::types {
private:
var_t m_value;
};
#endif
}
/// Manually lifetime managed Cow<T>
using either::Cow;
/// Wither an owned T, or a live, mutable reference to T.
template<typename T>
using MaybedOwnedRef = Cow<std::reference_wrapper<T>>; //XXX: Will we have to specialise Cow<> to make this work? \
TODO: I need to re-deisgn it to use a trait like `Borrow` and `ToOwned`, and implement the `owned_type` and `reference_type` to be specialised differently for different types without having to specialise the enture Cow<> struct.
/// RC-lifetime managed Cow<T>
template<typename T>
using AutoCow = Cow<std::shared_ptr<T>>;
using RcCow = Cow<std::shared_ptr<T>>;
/// Boxed unique Cow<T>
template<typename T>
using BoxCow = Cow<std::unique_ptr<T>>;
}

@ -1,31 +1,74 @@
#pragma once
#include <stdexcept>
#include <stacktrace>
#include <source_location>
#include "exopt.h"
#include "optional.h"
#include "either.h"
#include "util.hh"
namespace exopt {
// Report generated from an error. (virtual base, not abstract.)
class Report {
public:
virtual ~Report();
};
// This is propagated returned (base, abstract)
class Error {
constexpr Error() = default;
public:
constexpr Error(Error const&) = default;
constexpr Error(Error &&) = default;
constexpr Error& operator=(Error &&) = default;
constexpr Error& operator=(Error const&) = default;
constexpr virtual std::source_location const& location() const noexcept { return m_location; }
constexpr operator std::string_view() const noexcept { return message(); }
constexpr virtual std::string_view message() const noexcept =0;
constexpr virtual MaybeOwnedRef<Error> inner() noexcept { return {}; } //TODO: Maybe just use `std::optional<std::reference_wrapper<Error>>`
constexpr virtual MaybeOwned<Report> into_report() noexcept { /* TODO */ } // XXX: ^^
constexpr virtual MaybeOwned<Report> into_report() && noexcept { /* TODO: auto{*this}.into_report() */ }
constexpr virtual types::Option<Error&> inner() noexcept { return { std::nullopt }; }
constexpr virtual types::MaybeOwned<Report> into_report() noexcept { /* TODO */ } //TODO: Maybe just use `std::optional<std::reference_wrapper<Error>>`? Or Box<Report&>?
constexpr virtual types::MaybeOwned<Report> into_report() && noexcept { /* TODO: auto{*this}.into_report() */ }
constexpr virtual ~Error() = default;
protected:
constexpr Error(std::source_location loc = std::source_location::current()) noexcept
: m_location(std::move(loc)) {}
private:
std::source_location m_location;
//Moved this to non-constexpr subclass `TracedError` \
std::unique_ptr<std::stacktrace> m_trace{nullptr}; //TODO: Change to exopt::types::Box<> to make copy ctor/assign work
};
// This is propagated returned, along with a stack trace. (base, abstract)
class TracedError : Error {
public:
TracedError(TracedError const &) = default;
TracedError(TracedError &&) = default;
TracedError& operator=(TracedError &&) = default;
TracedError& operator=(TracedError const&) = default;
virtual ~TracedError() = default;
inline virtual std::stacktrace const& stacktrace() const noexcept { return m_trace; }
protected:
inline TracedError(
std::source_location loc = std::source_location::current()
, std::stacktrace trace = std::stacktrace::current()) noexcept
: Error(std::move(loc)), m_trace(std::move(trace)) {}
private:
std::stacktrace m_trace; //TODO: Maybe box this? idk how big it is...
};
// This is thrown. (virtual base, abstract)
class Panic : /*virtual?? I think it should be XXX*/ Error {
//TODO: How to make a `constexpr` deduced panic that has stack-trace at runtime only? Is it needed? Hnm...
};
//TODO: Fatal(string) : public virtual Panic
}

@ -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

@ -11,14 +11,15 @@
#include <vector>
#include <map>
#include <array>
#include <memory>
#include <span>
#include <tuple>
extern "C" {
#endif
size_t _EO(leven_diff)(const char* _EO(restrict), const char* _EO(restrict), size_t)
_EO(internal)
size_t _EO(leven_diff)(const char* _EO(restrict), const char* _EO(restrict), size_t)
_EO(internal)
_EO(readonly)
__attribute__((nonnull(1, 2)))
;
@ -134,12 +135,17 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
constexpr string_ord(std::add_rvalue_reference_t<S> str) noexcept requires(!std::is_array_v<string_type>)
: m_string(std::move(str)) {}
constexpr string_ord(const char (&str)[sizeof(string_type)]) requires(std::is_array_v<string_type>)
constexpr string_ord(const char (&str)[sizeof(string_type)]) noexcept requires(std::is_array_v<string_type>)
: string_ord(std::move(str), std::make_index_sequence<sizeof(string_type)>{}) {}
constexpr string_ord(const char (&&str)[sizeof(string_type)]) requires(std::is_array_v<string_type>)
constexpr string_ord(const char (&&str)[sizeof(string_type)]) noexcept requires(std::is_array_v<string_type>)
: string_ord(std::move(str), std::make_index_sequence<sizeof(string_type)>{}) {}
constexpr static string_ord from_char_array(std::array<char, sizeof(string_type)>&& l) noexcept requires(std::is_array_v<string_type>)
{
return { std::move(l) };
}
constexpr string_ord(string_ord const&) = default;
constexpr string_ord(string_ord &&) = default;
constexpr string_ord& operator=(string_ord const&) = default;
@ -190,6 +196,7 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
if constexpr(std::is_array_v<string_type>) {
struct OS : public OrderedString {
std::array<char, sizeof(string_type)> str;
constexpr static bool is_array() noexcept { return true; }
constexpr virtual ~OS() = default;
constexpr OS(string_type const& str) noexcept : OrderedString(), str(util::array_literal(std::forward<decltype(str)>(str))) {}
@ -207,6 +214,7 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
} else {
struct OS : public OrderedString {
string_type str;
constexpr static bool is_array() noexcept { return false; }
constexpr virtual ~OS() = default;
constexpr OS(string_type&& str) noexcept : OrderedString(), str(std::move(str)) {}
@ -227,6 +235,10 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
constexpr string_ord(const char (&&l)[sizeof(string_type)], std::index_sequence<Is...>) noexcept requires(std::is_array_v<string_type>)
: m_string { l[Is]... } {}
template<size_t... Is> requires(sizeof...(Is) == sizeof(string_type))
constexpr explicit string_ord(std::array<char, sizeof(string_type)>&& l, std::index_sequence<Is...>) noexcept requires(std::is_array_v<string_type>)
: m_string { l[Is]... } {}
string_type m_string;
};
template<size_t N>
@ -234,6 +246,72 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
template<typename T>
string_ord(T&&) -> string_ord<T>;
/// Create a dynamic instance of `string_ord<T>`.
/// This should be passed immediately to a call to `std::make_{unique/shared}` for most cases
template<typename T, typename... Args> requires(std::is_convertible_v<T, std::string_view> and std::is_constructible_v<T, Args...>)
constexpr decltype(auto) make_dynamic_ordered_string(Args&&... ctor) noexcept(std::is_nothrow_constructible_v<T, Args...>)
{
using str = string_ord<T>;
return str{ std::forward<Args>(ctor)... }.make_dynamic();
}
template<std::convertible_to<std::string_view> T>
constexpr decltype(auto) make_dynamic_ordered_string(T&& value) noexcept(std::is_nothrow_move_constructible_v<T>)
{
return string_ord<T>{ std::move(value) }.make_dynamic();
}
template<std::convertible_to<std::string_view> T>
constexpr auto make_unique_ordered_string(T&& value) noexcept(std::is_nothrow_move_constructible_v<T>)
{
auto&& tmp = make_dynamic_ordered_string<T>(std::move(value));
return std::make_unique<std::remove_reference_t<decltype(tmp)>>(std::move(tmp));
}
template<std::convertible_to<std::string_view> T>
constexpr auto make_shared_ordered_string(T&& value) noexcept(std::is_nothrow_move_constructible_v<T>)
{
auto&& tmp = make_dynamic_ordered_string<T>(std::move(value));
return std::make_shared<std::remove_reference_t<decltype(tmp)>>(std::move(tmp));
}
template<typename T, typename... Args> requires(std::is_convertible_v<T, std::string_view> and std::is_constructible_v<T, Args...>)
constexpr auto make_ordered_string(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
{
auto dyn = make_dynamic_ordered_string<T, Args...>(std::forward<Args>(args)...);
struct mover {
using type = std::remove_reference_t<decltype(dyn)>;
using string_type = decltype(dyn.str);
type value;
/* constexpr explicit operator auto() noexcept {
if constexpr(type::is_array())
return string_ord<string_type>::from_char_array(std::move(value.str));
else return string_ord<string_type>{ std::move(value.str) };
}*/
constexpr operator std::shared_ptr<type>() noexcept { return std::make_shared<type>(std::move(value)); }
constexpr operator std::unique_ptr<type>() && noexcept { return std::make_unique<type>(std::move(value)); }
constexpr std::unique_ptr<type> unique() && noexcept { return std::make_unique<type>(std::move(value)); }
constexpr std::shared_ptr<type> shared() noexcept { return this-> operator std::shared_ptr<type>(); }
constexpr ~mover() = default;
/*
constexpr string_type const&& exposed() const&& noexcept requires(!type::is_array()){ return std::move(value.str); }
constexpr string_type const& exposed() const& noexcept requires(!type::is_array()){ return value.str; }
constexpr string_type&& exposed() && noexcept requires(!type::is_array()) { return std::move(value.str); }
constexpr string_type exposed() & noexcept requires(!type::is_array()){ return value.str; }
//constexpr string_type const& exposed() const noexcept { return value.str; }
constexpr explicit operator type&&() && noexcept { return std::move(value); }
constexpr explicit operator string_type&&() && noexcept requires(!type::is_array()) { return std::move(value.str); }
constexpr explicit operator std::string_view() const noexcept { return { value }; }*/
};
return mover{std::move(dyn)};
}
/// Used to store all valid command names, so when an invalid one is found, the closest matching one(s) can be suggested to the user in a "did you mean ..." format with the *lowest difference* neighbour(s) to the invalid string first.
template<typename T, std::convertible_to<std::string_view> S = std::string_view>
using sim_map = std::map<string_ord<S>, T>;

@ -1,6 +1,6 @@
#pragma once
#include <optional>
#include <utility>
#include <concepts>
@ -21,6 +21,26 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
constexpr static inline auto sentinel = nullptr;
};
template<typename T>
struct null_optimise<std::reference_wrapper<T> > { constexpr static inline bool value = true;
using held_type = T*;
using type = std::reference_wrapper<T>;
constexpr static decltype(auto) convert_to_held(std::add_rvalue_reference_t<type> t) noexcept { return std::addressof(t.get()); }
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;
using held_type = T __restrict__*;
using type = ptr::Unique<T>;
constexpr static decltype(auto) convert_to_held(std::add_rvalue_reference_t<type> t) noexcept { return t.get(); }
constexpr static decltype(auto) convert_from_held(std::add_rvalue_reference_t<held_type> t) noexcept { return ptr::Unique<T>::new_unchecked(t); }
constexpr static inline auto sentinel = nullptr;
};
template<typename T>
struct null_optimise<T&> { constexpr static inline bool value = true;
using held_type = T*;
@ -223,6 +243,7 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
constexpr ~none_t() noexcept = default;
constexpr none_t(std::nullptr_t) noexcept : none_t() {}
constexpr none_t(std::nullopt_t) noexcept : none_t() {}
constexpr operator std::nullptr_t() const noexcept { return nullptr; }
};
@ -258,6 +279,14 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
constexpr static inline bool is_null_optimised = has_null_opt<T>;
constexpr friend void swap(Option& a, Option& b) noexcept {
using std::swap;
swap(a.inner_, b.inner_); //XXX: Swap the values if possible?
}
constexpr Option(std::optional<T>&& opt) noexcept requires(!std::is_void_v<T>)
: inner_( bool(opt) ? std::move(*opt) : nullptr ) {}
constexpr explicit Option(std::nullptr_t) noexcept : inner_(nullptr) {}
constexpr Option(none_t) noexcept : Option(nullptr) {}
constexpr ~Option() noexcept(std::is_nothrow_destructible_v<T>) {}
@ -531,10 +560,30 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
// ---
};
template<typename T, typename U> requires(requires(T&& p) { static_cast<U&&>(p); })
constexpr Option<U> static_opt_cast(Option<T>&& p) noexcept
{
if(bool(p)) return Option<U> { static_cast<U&&>(std::move(*p)) };
return Option<U> { nullptr };
}
template<typename T, typename U> requires(requires(T&& p) { static_cast<U&&>(p); })
constexpr Option<U> dynamic_opt_cast(Option<T>&& p)
{
if(T* ptr = p.try_get_value())
if(U* pv = dynamic_cast<U*>(ptr))
return Option<U> { std::move(*pv) };
return Option<U> { nullptr };
}
static_assert(sizeof(Option<void*>) > sizeof(void*), "Option<T*>: Bad null_opt size");
static_assert(alignof(Option<void*>) == alignof(void*), "Option<T*>: Bad null_opt align");
static_assert(sizeof(Option<ptr::NonNull<int>>) == sizeof(int*), "Option<NonNull<T>>: Bad null_opt size");
static_assert(alignof(Option<ptr::NonNull<int>>) == alignof(int*), "Option<NonNull<T>>: Bad null_opt align");
static_assert(sizeof(Option<ptr::Unique<int>>) == sizeof(int*), "Option<Unique<T>>: Bad null_opt size");
static_assert(alignof(Option<ptr::Unique<int>>) == alignof(int*), "Option<Unique<T>>: Bad null_opt align");
static_assert(sizeof(Option<double&>) == sizeof(double&), "Option<T&>: Bad null_opt size");
static_assert(alignof(Option<double&>) == alignof(double*), "Option<T&>: Bad null_opt align");
static_assert(sizeof(Option<long long int&&>) == sizeof(char*), "Option<T&&>: Bad null_opt size");
@ -545,4 +594,5 @@ namespace exopt::types { namespace optional [[gnu::visibility("internal")]] {
}
using optional::Option;
}

@ -1,8 +1,15 @@
#pragma once
#include <utility>
#include <functional>
#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>
@ -11,15 +18,24 @@ namespace exopt::ptr {
using RawRef = T&;
template<typename T>
using UniquePtr = T* __restrict__;
using UniquePtr = T __restrict__*;
template<typename T>
using UniqueRef = T __restrict__&;
template<typename T>
using UniqueRef = T& __restrict__;
using UniqueMoveRef = T __restrict__&&;
template<typename T>
using AliasedPtr = T __attribute__((__may_alias__))*;
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;
@ -88,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)
{
@ -127,17 +170,68 @@ 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 final {
struct NonNull {
using pointer_type = T*;
using const_pointer_type = std::remove_cv_t<T> const*;
constexpr static inline bool is_mutable = !std::is_same_v<pointer_type, const_pointer_type>;
constexpr NonNull(T& ref) noexcept
: ptr_(std::addressof(ref)) {}
[[gnu::nonnull(1, 2)]]
@ -150,15 +244,7 @@ namespace exopt::ptr {
constexpr ~NonNull() noexcept = default;
constexpr static NonNull<T> try_new(T* ptr) {
constexpr auto _throw = [] [[noreturn, gnu::noinline, gnu::cold]] () {
if consteval {
throw NullException{};
} else {
throw std::runtime_error(NullException{}.message());
}
//throw error::as_runtime_error(error::comptime_fail<NullException>());
};
if(!ptr) _throw();
if(__builtin_expect(!ptr, false)) _throw_null();
return NonNull<T>{ptr};
}
#ifdef DEBUG
@ -176,16 +262,51 @@ namespace exopt::ptr {
constexpr friend bool operator!=(std::nullptr_t, const NonNull&) noexcept { return true; }
constexpr friend bool operator!=(const NonNull&, std::nullptr_t) noexcept { return true; }
constexpr friend auto operator<=>(const NonNull& a, const NonNull& b) noexcept { return a.ptr_ <=> b.ptr_; }
constexpr friend auto operator<=>(const NonNull& a, T* b) noexcept { return (!b) ? (true<=>false) : a.ptr_ <=> b; }
constexpr friend auto operator<=>(T* a, const NonNull& b) noexcept { return (!a) ? (false<=>true) : (a <=> b.ptr_); }
constexpr friend auto operator<=>(const NonNull& a, const NonNull& b) noexcept { return a.get() <=> b.get(); }
constexpr friend auto operator<=>(const NonNull& a, T* b) noexcept { return (!b) ? (true<=>false) : (a.get() <=> b); }
constexpr friend auto operator<=>(T* a, const NonNull& b) noexcept { return (!a) ? (false<=>true) : (a <=> b.get()); }
template<typename U>
constexpr NonNull<U> cast() const noexcept requires(requires(T* ptr) { static_cast<U*>(ptr); })
{
return NonNull<U>::new_unchecked(static_cast<U*>(get()));
}
template<typename U>
constexpr NonNull<U> try_cast() const requires(requires(T* ptr) { dynamic_cast<U*>(ptr); })
{
return NonNull<U>::try_new(dynamic_cast<U*>(get()));
}
template<typename Func> requires(std::is_invocable_v<Func, T&>)
constexpr auto map_value(Func const& mapper) & noexcept(std::is_nothrow_invocable_v<Func, T&>)
-> NonNull<std::add_pointer_t<std::invoke_result_t<Func, T&>>> //TODO: Can we extend this for: void returns, pointer (not reference) returns, maybe even std::convertible_to<?*> returns?
{ return { std::addressof(std::invoke(std::forward<decltype(mapper)>(mapper), static_cast<T &>(*get()))) }; }
template<typename Func> requires(std::is_invocable_v<Func, T const&>)
constexpr auto map_value(Func const& mapper) const& noexcept(std::is_nothrow_invocable_v<Func, T const&>)
-> NonNull<std::add_pointer_t<std::invoke_result_t<Func, T const&>>> //TODO: Can we extend this for: void returns, pointer (not reference) returns, maybe even std::convertible_to<?*> returns?
{ return { std::addressof(std::invoke(std::forward<decltype(mapper)>(mapper), static_cast<T const&>(*get()))) }; }
template<typename Func> requires(std::is_invocable_v<Func, T&&>)
constexpr auto map_value(Func&& mapper) && noexcept(std::is_nothrow_invocable_v<Func, T&&>)
-> NonNull<std::add_pointer_t<std::invoke_result_t<Func, T&&>>> //TODO: Can we extend this for: void returns, pointer (not reference) returns, maybe even std::convertible_to<?*> returns?
{ return { std::addressof(std::invoke(std::forward<decltype(mapper)>(mapper), std::move(*get()))) }; }
template<typename Func> requires(std::is_invocable_v<Func, T const&&>)
constexpr auto map_value(Func&& mapper) const&& noexcept(std::is_nothrow_invocable_v<Func, T const&&>)
-> NonNull<std::add_pointer_t<std::invoke_result_t<Func, T const&&>>> //TODO: Can we extend this for: void returns, pointer (not reference) returns, maybe even std::convertible_to<?*> returns?
{ return { std::addressof(std::invoke(std::forward<decltype(mapper)>(mapper), std::move(static_cast<T const&>(*get())))) }; }
template<typename U>
constexpr explicit operator NonNull<U>() const noexcept requires(requires(T* ptr) { static_cast<U*>(ptr); })
{
return cast<U>();
}
[[gnu::returns_nonnull]]
constexpr T* get() const noexcept { return ptr_; }
/*
[[gnu::returns_nonnull]]
constexpr T* const&& get() const&& noexcept { return std::move(ptr_); }
*/
[[gnu::returns_nonnull]]
constexpr operator T*() const noexcept { return ptr_; }
@ -198,7 +319,113 @@ namespace exopt::ptr {
constexpr T* operator->() noexcept { return ptr_; }
[[gnu::returns_nonnull]]
constexpr const T* operator->() const noexcept { return ptr_; }
private:
private:
[[noreturn, gnu::noinline, gnu::cold]]
constexpr static void _throw_null() {
if consteval {
throw NullException{};
} else {
//std::terminate();
throw NullException{}.as_runtime();
//::exopt::util::throw_runtime(NullException{}); //XXX: Wut?
}
//throw error::as_runtime_error(error::comptime_fail<NullException>());
}
T* ptr_;
};
/// Holds a *unique reference* that cannot be aliased, and cannot be null.
template<typename T> requires(!std::is_reference_v<T>)
struct Unique {
using value_type = std::remove_cv_t<std::decay_t<T>>;
using reference_type = UniqueRef<value_type>;
using const_reference_type = UniqueRef<value_type const>;
using move_reference_type = UniqueMoveRef<value_type>;
using pointer_type = UniquePtr<value_type>;
using const_pointer_type = UniquePtr<value_type const>;
constexpr static inline bool is_mutable = !std::is_same_v<pointer_type, const_pointer_type>;
constexpr static Unique<T> try_new(pointer_type const&& ptr)
{
return { NonNull<T>::try_new(ptr) };
}
#ifdef DEBUG
[[gnu::nonnull(1)]]
#endif
constexpr static Unique<T> new_unchecked(pointer_type ptr) noexcept {
#ifndef DEBUG
_EO_ASSUME(ptr!=nullptr);
#endif
return { NonNull<T>{ ptr } }; }
constexpr Unique(std::convertible_to<NonNull<value_type>> auto const&& ptr) noexcept
: m_ptr(ptr) {}
[[gnu::nonnull(1,2)]]
constexpr explicit Unique(T* __restrict__ ptr) noexcept
: m_ptr(ptr) {}
constexpr Unique(Unique&&) noexcept = default;
constexpr Unique& operator=(Unique&&) noexcept = default;
constexpr Unique(const Unique&) = delete;
constexpr Unique& operator=(const Unique&) = delete;
constexpr ~Unique() noexcept = default;
constexpr friend bool operator==(std::nullptr_t, const Unique& __restrict__) noexcept { return false; }
constexpr friend bool operator==(const Unique& __restrict__, std::nullptr_t) noexcept { return false; }
constexpr friend bool operator!=(std::nullptr_t, const Unique& __restrict__) noexcept { return true; }
constexpr friend bool operator!=(const Unique& __restrict__, std::nullptr_t) noexcept { return true; }
constexpr friend auto operator<=>(const Unique& __restrict__ a, const Unique& __restrict__ b) noexcept { return a.get() <=> b.get(); }
constexpr friend auto operator<=>(const Unique& __restrict__ a, T* __restrict__ b) noexcept { return (!b) ? (true<=>false) : (a.get() <=> b); }
constexpr friend auto operator<=>(T* __restrict__ a, const Unique& __restrict__ b) noexcept { return (!a) ? (false<=>true) : (a <=> b.get()); }
[[gnu::returns_nonnull]]
constexpr pointer_type get() const __restrict__ noexcept { return m_ptr.get(); }
[[gnu::returns_nonnull]]
constexpr operator pointer_type() const __restrict__ noexcept { return get(); }
constexpr explicit operator NonNull<T>() const noexcept { return m_ptr; }
constexpr reference_type operator*() __restrict__ & noexcept { return *get(); }
constexpr const_reference_type operator*() const __restrict__& noexcept { return *get(); }
constexpr move_reference_type operator*() __restrict__ &&noexcept { return std::move(*get()); }
constexpr decltype(auto) operator*() const __restrict__ &&noexcept { return *get(); }
[[gnu::returns_nonnull]]
constexpr pointer_type operator->() __restrict__ noexcept { return get(); }
[[gnu::returns_nonnull]]
constexpr const_pointer_type operator->() const __restrict__ noexcept { return get(); }
private:
NonNull<T> m_ptr;
};
#if 0
consteval bool uniq_check() noexcept {
int ptr = 0;
int* const ptr0 = &ptr;
return Unique<int>::try_new(std::move(ptr0)).get() == &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

@ -12,6 +12,7 @@
#define _EO_CONSTANT_VALUE(X) ([]() { \
struct { \
typedef decltype(X) comptime_constant_t; \
constexpr static inline auto VALUE = (X); \
consteval operator comptime_constant_t() const noexcept { return (X); } \
} inner; \
return inner; \
@ -28,7 +29,7 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
constexpr auto comptime_value(auto value) noexcept {
using U = decltype(value);
struct inner {
struct inner final {
typedef U comptime_constant_t;
consteval operator comptime_constant_t() const noexcept { return std::move(value); }
U value;
@ -38,11 +39,41 @@ namespace exopt { namespace util [[gnu::visibility("internal")]] {
};
return inner{ std::move(value) };
}
template<typename T, auto value> requires(comptime<decltype(value), T>)
consteval T comptime_eval() noexcept {
if constexpr(requires {
{ decltype(value)::VALUE } -> std::convertible_to<decltype(value)::comptime_constant_t>;
}) {
return decltype(value)::VALUE;
} else return value;
}
template<typename T, auto value> requires(comptime<decltype(value), T>)
constexpr inline T comptime_eval_v = comptime_eval<T, value>();
template<typename T>
consteval T comptime_eval(comptime<T> auto value) noexcept {
return comptime_eval<T, value>();
}
static_assert(comptime_eval(comptime_value(5.0)) == 5.0, "Bad comptime_eval() consteval");
static_assert(comptime_eval(_EO_CONSTANT_VALUE(3.f)) == 3.f, "Bad comptime_eval() macro");
static_assert(std::is_same_v<decltype(comptime_eval(comptime_value(4.f))), float>, "Bad comptime_eval() return type consteval");
static_assert(std::is_same_v<decltype(comptime_eval(_EO_CONSTANT_VALUE(4.0))), double>, "Bad comptime_eval() return type macro");
static_assert(comptime_value(std::string_view{"hello"}.size()) == 5, "Bad comptime_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>>
constexpr auto array_literal(const T (&a)[N]) noexcept
-> Array
@ -63,4 +94,341 @@ 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;
constexpr CTInternalFatalError(CTInternalFatalError &&) noexcept = default;
constexpr CTInternalFatalError& operator=(CTInternalFatalError const&) noexcept = default;
constexpr CTInternalFatalError& operator=(CTInternalFatalError&&) noexcept = default;
constexpr virtual ~CTInternalFatalError() noexcept = default;
constexpr operator std::string_view() const noexcept { return message(); }
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) {
if consteval {
#define CTE CTInternalFatalError
using S = decltype(msg);
struct CTIFRuntimeError final : public CTE {
using CTE::CTE;
consteval CTIFRuntimeError(S&& view) noexcept
: CTE(), m_message(std::move(view)) {}
consteval std::string_view message() const noexcept override { return { m_message }; }
constexpr virtual ~CTIFRuntimeError() noexcept {}
private:
S m_message;
};
#undef CTE
throw CTIFRuntimeError(std::move(msg));
} else {
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 {
union UI {
struct assume{};
T value;
[[no_unique_address]] assume init{};
};
return UI{};
}
template<typename T> requires(!std::is_reference_v<T>)
struct alignas(T) maybe_uninit {
using uninit = std::array<unsigned char, sizeof(T)>;
constexpr maybe_uninit() noexcept {}
constexpr maybe_uninit(const maybe_uninit& c) noexcept
: m_uninit(c.m_uninit) {}
constexpr maybe_uninit(maybe_uninit&& m) noexcept
: m_uninit(std::move(m.m_uninit)) {}
constexpr maybe_uninit& operator=(maybe_uninit const& c) noexcept
{ m_uninit = c.m_uninit; return *this; }
constexpr maybe_uninit& operator=(maybe_uninit&& c) noexcept
{ m_uninit = std::move(c.m_uninit); return *this; }
constexpr T& operator=(T&& value) noexcept {
m_init = std::move(value); return *this;
}
constexpr T& operator=(T const& copy) noexcept {
m_init = maybe_uninit{copy}; return *this; //XXX: These assignment operators may not be a good idea, we can't guarantee which active destriminant it is.
}
constexpr T& assume_init() & noexcept = delete;
constexpr T&& assume_init() && noexcept { return std::move(m_init); }
constexpr T const& assume_init() const& noexcept { return m_init; }
constexpr T const&& assume_init() const&& noexcept { return m_init; }
constexpr uninit& data() &noexcept { return m_uninit; }
constexpr uninit&& data() &&noexcept { return std::move(m_uninit); }
constexpr uninit const& data() const&noexcept { return m_uninit; }
constexpr uninit const&& data() const&& noexcept { return std::move(m_uninit); }
constexpr T* get() noexcept { return std::addressof(m_init); }
constexpr const T* get() const noexcept { return std::addressof(m_init); }
//TODO: accessors, `assume_init_*()`, etc.
constexpr ~maybe_uninit() noexcept {}
constexpr void assume_init_drop() noexcept(std::is_nothrow_destructible_v<T>)
{ m_init.~T(); m_uninit = {}; }
[[nodiscard]]
constexpr T assume_init_take() noexcept(std::is_nothrow_move_constructible_v<T>)
{
auto v = std::move(m_init);
assume_init_drop();
return v;
}
constexpr maybe_uninit<T> init(T&& value) noexcept(std::is_nothrow_move_constructible_v<T>)
{ return maybe_uninit<T>{std::move(value), EXPLICIT_INIT_TAG<EXPLICIT_INIT_MOVE>{}}; }
template<typename... Args> requires(std::is_constructible_v<T, Args...>)
constexpr maybe_uninit<T> init_in_place(Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
{ return maybe_uninit<T>{EXPLICIT_INIT_TAG<EXPLICIT_INIT_IN_PLACE>{}, std::forward<Args>(args)...}; }
private:
enum EXPLICIT_INIT_TYPE {
EXPLICIT_INIT_IN_PLACE,
EXPLICIT_INIT_MOVE,
};
template<EXPLICIT_INIT_TYPE>
struct EXPLICIT_INIT_TAG {};
constexpr maybe_uninit(T&& value, EXPLICIT_INIT_TAG<EXPLICIT_INIT_MOVE>) noexcept(std::is_nothrow_move_constructible_v<T>)
: m_init{std::move(value)} {}
template<typename... Args> requires(std::is_constructible_v<T, Args...>)
constexpr maybe_uninit(EXPLICIT_INIT_TAG<EXPLICIT_INIT_IN_PLACE> _a, Args&&... args) noexcept(std::is_nothrow_constructible_v<T, Args...>)
: m_init{std::forward<Args>(args)...} { (void) _a; }
union {
T m_init;
[[no_unique_address]] alignas(T) uninit m_uninit{};
};
};
namespace {
constexpr std::string_view ui_test() noexcept {
auto ui = uninit<std::string_view>();
maybe_uninit<std::string_view> mi;
ui.init.~assume();
ui.value = std::string_view{"Hello"};
return std::move(ui.value);
}
static_assert(ui_test()[0] == 'H', "Bad uninit<T>()");
}
} }

@ -22,6 +22,11 @@ namespace exopt {
constexpr string_ord a("hello world");
constexpr string_ord c{a.view()};
constexpr string_ord b{""};
consteval bool check_dynamic() noexcept {
auto d = make_ordered_string<decltype("hello")>("hello").unique();
return d->size() == (sizeof("hello") -1);
}
static_assert(check_dynamic(), "Levenshtein: Bad dynamic sizing");
static_assert(a.view()[a.view().size()-1], "Levenshtein distance size mismatch for array literals");
static_assert(a.difference_from(c) == 0, "Levenshtein distance mismatch when converting from array literal");
static_assert(a.make_dynamic().view() == c.make_dynamic(), "Levenshtein distance mismatch when converting from array literal to dynamic");

@ -0,0 +1,20 @@
#include <string>
#include "pointer.h"
namespace exopt::ptr {
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.)
}
}
}

@ -0,0 +1,15 @@
#include <string>
#include <stdexcept>
#include <util.hh>
namespace exopt {
namespace util [[gnu::visibility("internal")]] {
[[noreturn, gnu::noinline, gnu::cold]]
void throw_runtime(std::string_view&& msg) {
throw std::runtime_error(std::string{std::move(msg)});
}
}
}
Loading…
Cancel
Save