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

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