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

334 lines
14 KiB

#pragma once
#include <utility>
#include <functional>
#include <concepts>
#include <bit>
#include <stdexcept>
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__))&;
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)...);
}
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 {}
constexpr std::string_view message() const noexcept /*override*/ { return "pointer was null"; }
std::runtime_error as_runtime() &&noexcept; /*TODO: override*/
};
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
}