You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
398 lines
16 KiB
398 lines
16 KiB
#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>
|
|
using RawPtr = T*;
|
|
template<typename T>
|
|
using RawRef = T&;
|
|
|
|
template<typename T>
|
|
using UniquePtr = T __restrict__*;
|
|
template<typename T>
|
|
using UniqueRef = T __restrict__&;
|
|
template<typename T>
|
|
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;
|
|
|
|
template<typename T>
|
|
concept PointerEqCmp = requires(T p) {
|
|
{ p == nullptr } -> std::convertible_to<bool>;
|
|
};
|
|
|
|
template<typename T>
|
|
concept PointerMemCmp = PointerEqCmp<T> && requires(T p, T p2) {
|
|
{ p == p2 } -> std::convertible_to<bool>;
|
|
{ static_cast<void*>(p) };
|
|
};
|
|
|
|
template<typename T>
|
|
concept PointerDeref = PointerEqCmp<T> && requires(T p) {
|
|
{ *p };
|
|
};
|
|
template<typename T, typename To>
|
|
concept PointerDerefTo = PointerDeref<T> && requires(T p) {
|
|
{ *p } -> std::convertible_to<To>;
|
|
};
|
|
|
|
template<PointerEqCmp T>
|
|
constexpr decltype(auto) map(T&& ptr, auto&& func) noexcept(std::is_nothrow_invocable_v<decltype(func), decltype(ptr)>) requires(std::is_invocable_v<decltype(func), decltype(ptr)>)
|
|
{
|
|
if (ptr == nullptr) return std::forward<T>(ptr);
|
|
else return func(std::forward<T>(ptr));
|
|
}
|
|
|
|
template<PointerEqCmp T>
|
|
constexpr decltype(auto) map(T&& ptr, auto&& func, auto&& other) noexcept(std::is_nothrow_invocable_v<decltype(func), decltype(ptr)> && (!std::is_invocable_v<decltype(other)> || std::is_nothrow_invocable_v<decltype(other)>)) requires(std::is_invocable_v<decltype(func), decltype(ptr)>)
|
|
{
|
|
if (ptr == nullptr) return std::forward<T>(ptr);
|
|
else if constexpr(std::is_invocable_v<decltype(other)>) return other();
|
|
else return std::forward<decltype(other)>(other);
|
|
}
|
|
|
|
template<PointerEqCmp T>
|
|
constexpr decltype(auto) not_null_or(T&& ptr, auto&& other) noexcept(!std::is_invocable_v<decltype(other)>) requires(std::is_convertible_v<decltype(other), T> || (std::is_invocable_v<decltype(other)> && std::is_convertible_v<std::invoke_result_t<decltype(other)>, decltype(ptr)>))
|
|
{
|
|
if(!(ptr == nullptr)) return std::forward<T>(ptr);
|
|
else if constexpr(std::is_invocable_v<decltype(other)>) return other();
|
|
else return std::forward<decltype(other)>(other);
|
|
}
|
|
|
|
template<typename T, typename U>
|
|
constexpr bool addr_eq(const T& o1, const U& o2) noexcept { return static_cast<void*>(std::addressof(o1)) == static_cast<void*>(std::addressof(o2)); }
|
|
|
|
template<typename U>
|
|
constexpr bool ptr_addr_eq(const PointerMemCmp auto& ptr, const U& obj) noexcept { return static_cast<void*>(ptr) == static_cast<void*>(std::addressof(obj)); }
|
|
|
|
template<PointerDeref U>
|
|
constexpr decltype(auto) deref_or(U&& ptr, auto&& other) noexcept(noexcept(*ptr) && (!std::is_invocable_v<decltype(other)> || std::is_nothrow_invocable_v<decltype(other)>)) {
|
|
if (!(ptr == nullptr)) return *ptr;
|
|
else if constexpr (std::is_invocable_v<decltype(other)>) return other();
|
|
else return std::forward<decltype(other)>(other);
|
|
}
|
|
|
|
template<typename E, typename... Args> requires(std::is_constructible_v<E, Args...>)
|
|
constexpr decltype(auto) throw_if_null(PointerEqCmp auto&& 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);
|
|
}
|
|
|
|
template<typename E, typename... Args> requires(std::is_constructible_v<E, Args...>)
|
|
constexpr decltype(auto) deref_or_throw(PointerDeref auto&& ptr, Args&&... error)
|
|
{
|
|
return *throw_if_null<E, Args...>(std::forward<decltype(ptr)>(ptr), std::forward<Args>(error)...);
|
|
}
|
|
|
|
template<typename T>
|
|
constexpr auto read_once(const T& ref) noexcept(std::is_nothrow_copy_constructible_v<T>) requires(std::is_copy_constructible_v<T>)
|
|
{
|
|
AliasedPtr<const T> aref = std::addressof(ref);
|
|
return *aref;
|
|
}
|
|
|
|
template<typename T>
|
|
constexpr decltype(auto) write_once(T& ref, std::convertible_to<T> auto&& value) noexcept(std::is_nothrow_move_constructible_v<T>) requires(std::is_move_constructible_v<decltype(value)> && std::is_constructible_v<T, decltype(std::move(value))>)
|
|
{
|
|
AliasedPtr<T> aref = std::addressof(ref);
|
|
return *aref = std::move(value);
|
|
|
|
}
|
|
|
|
template<typename T>
|
|
constexpr T read_once_strict(const T& ref) noexcept(std::is_nothrow_copy_constructible_v<T>) requires(std::is_copy_constructible_v<T>)
|
|
{
|
|
return *static_cast<const volatile T*>(std::addressof(ref));
|
|
}
|
|
|
|
template<typename T>
|
|
constexpr void write_once_strict(T& ref, std::convertible_to<T> auto&& value) noexcept(std::is_nothrow_move_constructible_v<T>) requires(std::is_move_constructible_v<decltype(value)> && std::is_constructible_v<T, decltype(std::move(value))>)
|
|
{
|
|
*static_cast<volatile T*>(std::addressof(ref)) = T{std::move(value)};
|
|
}
|
|
|
|
template<typename T, typename... Args> requires(std::is_constructible_v<T, Args...>)
|
|
constexpr T& leak(Args&&... ctor) noexcept(std::is_nothrow_constructible_v<T, Args...>)
|
|
{
|
|
return *new T(std::forward<Args>(ctor)...);
|
|
}
|
|
|
|
template<typename... Types>
|
|
struct NullDerefException : public NullDerefException<> {
|
|
using types = std::tuple<Types...>;
|
|
using NullDerefException<>::NullDerefException;
|
|
|
|
constexpr std::string_view message() const noexcept override {
|
|
/* constexpr auto value = util::concat_strings("pointer was null for types ", util::type_name<Types>()...);
|
|
return value;*/
|
|
return util::concat_str_v< std::string_view{"pointer was null for types "}, util::type_name<Types>()... >;
|
|
}
|
|
constexpr virtual ~NullDerefException() noexcept {}
|
|
};
|
|
|
|
template<>
|
|
struct NullDerefException<> /*TODO : public error::Error*/ {
|
|
constexpr NullDerefException() noexcept = default;
|
|
constexpr NullDerefException(const NullException&) noexcept = default;
|
|
constexpr NullDerefException& operator=(const NullException&) noexcept = default;
|
|
constexpr virtual ~NullDerefException() noexcept {}
|
|
|
|
constexpr virtual std::string_view message() const noexcept /*override*/ { return "pointer was null"; }
|
|
|
|
std::runtime_error as_runtime() &&noexcept; /*TODO: override*/
|
|
};
|
|
|
|
using NullException = NullDerefException<>;
|
|
|
|
template<typename T>
|
|
struct NullDerefException<T> : public NullDerefException<> {
|
|
using type = T;
|
|
using NullDerefException<>::NullDerefException;
|
|
|
|
constexpr std::string_view message() const noexcept override {
|
|
return util::concat_str_v< std::string_view{"pointer was null for type "}, util::type_name<type>() >;
|
|
}
|
|
constexpr virtual ~NullDerefException() noexcept {}
|
|
};
|
|
|
|
template<typename... Types>
|
|
consteval auto/*&&*/ null_exception() noexcept {
|
|
/*NullException&& value = NullDerefException<Types...>{};
|
|
return std::move(value); <- this isn't actually what we want I'm pretty sure...*/
|
|
return NullDerefException<Types...>{};
|
|
}
|
|
|
|
consteval NullException/*&&*/ null_exception() noexcept { return {}; }
|
|
|
|
template<typename T> requires(!std::is_reference_v<T>)
|
|
struct NonNull {
|
|
|
|
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)]]
|
|
constexpr explicit NonNull(T* ptr) noexcept
|
|
: ptr_(ptr) {}
|
|
constexpr NonNull(const NonNull&) noexcept = default;
|
|
constexpr NonNull(NonNull&&) noexcept = default;
|
|
constexpr NonNull& operator=(const NonNull&) noexcept = default;
|
|
constexpr NonNull& operator=(NonNull&&) noexcept = default;
|
|
constexpr ~NonNull() noexcept = default;
|
|
|
|
constexpr static NonNull<T> try_new(T* ptr) {
|
|
if(__builtin_expect(!ptr, false)) _throw_null();
|
|
return NonNull<T>{ptr};
|
|
}
|
|
#ifdef DEBUG
|
|
[[gnu::nonnull(1)]]
|
|
#endif
|
|
constexpr static NonNull<T> new_unchecked(T* ptr) noexcept {
|
|
#ifndef DEBUG
|
|
_EO_ASSUME(ptr!=nullptr);
|
|
#endif
|
|
return NonNull{ptr}; }
|
|
|
|
constexpr friend bool operator==(std::nullptr_t, const NonNull&) noexcept { return false; }
|
|
constexpr friend bool operator==(const NonNull&, std::nullptr_t) noexcept { return false; }
|
|
|
|
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.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 operator T*() const noexcept { return ptr_; }
|
|
|
|
constexpr T& operator*() & noexcept { return *ptr_; }
|
|
constexpr const T& operator*() const& noexcept { return *ptr_; }
|
|
constexpr T&& operator*() && noexcept { return std::move(*ptr_); }
|
|
constexpr decltype(auto) operator*() const&& noexcept { return *ptr_; }
|
|
|
|
[[gnu::returns_nonnull]]
|
|
constexpr T* operator->() noexcept { return ptr_; }
|
|
[[gnu::returns_nonnull]]
|
|
constexpr const T* operator->() const noexcept { return ptr_; }
|
|
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
|