#pragma once #include #include #include #include #include #include #include "util.hh" #define _EO_DETAILS namespace details [[gnu::visibility("internal")]] namespace exopt::ptr { template using RawPtr = T*; template using RawRef = T&; template using UniquePtr = T __restrict__*; template using UniqueRef = T __restrict__&; template using UniqueMoveRef = T __restrict__&&; template using AliasedPtr = T __attribute__((__may_alias__))*; template 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) == sizeof(address_t), "Bad address_t width"); template concept IsSize = sizeof(T) == Size; template concept PointerEqCmp = requires(T p) { { p == nullptr } -> std::convertible_to; }; template concept PointerMemCmp = PointerEqCmp && requires(T p, T p2) { { p == p2 } -> std::convertible_to; { static_cast(p) }; }; template concept PointerDeref = PointerEqCmp && requires(T p) { { *p }; }; template concept PointerDerefTo = PointerDeref && requires(T p) { { *p } -> std::convertible_to; }; template constexpr decltype(auto) map(T&& ptr, auto&& func) noexcept(std::is_nothrow_invocable_v) requires(std::is_invocable_v) { if (ptr == nullptr) return std::forward(ptr); else return func(std::forward(ptr)); } template constexpr decltype(auto) map(T&& ptr, auto&& func, auto&& other) noexcept(std::is_nothrow_invocable_v && (!std::is_invocable_v || std::is_nothrow_invocable_v)) requires(std::is_invocable_v) { if (ptr == nullptr) return std::forward(ptr); else if constexpr(std::is_invocable_v) return other(); else return std::forward(other); } template constexpr decltype(auto) not_null_or(T&& ptr, auto&& other) noexcept(!std::is_invocable_v) requires(std::is_convertible_v || (std::is_invocable_v && std::is_convertible_v, decltype(ptr)>)) { if(!(ptr == nullptr)) return std::forward(ptr); else if constexpr(std::is_invocable_v) return other(); else return std::forward(other); } template constexpr bool addr_eq(const T& o1, const U& o2) noexcept { return static_cast(std::addressof(o1)) == static_cast(std::addressof(o2)); } template constexpr bool ptr_addr_eq(const PointerMemCmp auto& ptr, const U& obj) noexcept { return static_cast(ptr) == static_cast(std::addressof(obj)); } template constexpr decltype(auto) deref_or(U&& ptr, auto&& other) noexcept(noexcept(*ptr) && (!std::is_invocable_v || std::is_nothrow_invocable_v)) { if (!(ptr == nullptr)) return *ptr; else if constexpr (std::is_invocable_v) return other(); else return std::forward(other); } template requires(std::is_constructible_v) constexpr decltype(auto) throw_if_null(PointerEqCmp auto&& ptr, Args&&... error) { auto _throw = [&] [[noreturn, gnu::noinline, gnu::cold]] () { throw E{std::forward(error)...}; }; if(ptr == nullptr) _throw(); return std::forward(ptr); } template requires(std::is_constructible_v) constexpr decltype(auto) deref_or_throw(PointerDeref auto&& ptr, Args&&... error) { return *throw_if_null(std::forward(ptr), std::forward(error)...); } template constexpr auto read_once(const T& ref) noexcept(std::is_nothrow_copy_constructible_v) requires(std::is_copy_constructible_v) { AliasedPtr aref = std::addressof(ref); return *aref; } template constexpr decltype(auto) write_once(T& ref, std::convertible_to auto&& value) noexcept(std::is_nothrow_move_constructible_v) requires(std::is_move_constructible_v && std::is_constructible_v) { AliasedPtr aref = std::addressof(ref); return *aref = std::move(value); } template constexpr T read_once_strict(const T& ref) noexcept(std::is_nothrow_copy_constructible_v) requires(std::is_copy_constructible_v) { return *static_cast(std::addressof(ref)); } template constexpr void write_once_strict(T& ref, std::convertible_to auto&& value) noexcept(std::is_nothrow_move_constructible_v) requires(std::is_move_constructible_v && std::is_constructible_v) { *static_cast(std::addressof(ref)) = T{std::move(value)}; } template requires(std::is_constructible_v) constexpr T& leak(Args&&... ctor) noexcept(std::is_nothrow_constructible_v) { return *new T(std::forward(ctor)...); } template struct NullDerefException : public NullDerefException<> { using types = std::tuple; 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()...); return value;*/ return util::concat_str_v< std::string_view{"pointer was null for types "}, util::type_name()... >; } 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 struct NullDerefException : 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() >; } constexpr virtual ~NullDerefException() noexcept {} }; template consteval auto/*&&*/ null_exception() noexcept { /*NullException&& value = NullDerefException{}; return std::move(value); <- this isn't actually what we want I'm pretty sure...*/ return NullDerefException{}; } consteval NullException/*&&*/ null_exception() noexcept { return {}; } template requires(!std::is_reference_v) struct NonNull { using pointer_type = T*; using const_pointer_type = std::remove_cv_t const*; constexpr static inline bool is_mutable = !std::is_same_v; 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 try_new(T* ptr) { if(__builtin_expect(!ptr, false)) _throw_null(); return NonNull{ptr}; } #ifdef DEBUG [[gnu::nonnull(1)]] #endif constexpr static NonNull 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 constexpr NonNull cast() const noexcept requires(requires(T* ptr) { static_cast(ptr); }) { return NonNull::new_unchecked(static_cast(get())); } template constexpr NonNull try_cast() const requires(requires(T* ptr) { dynamic_cast(ptr); }) { return NonNull::try_new(dynamic_cast(get())); } template requires(std::is_invocable_v) constexpr auto map_value(Func const& mapper) & noexcept(std::is_nothrow_invocable_v) -> NonNull>> //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(mapper), static_cast(*get()))) }; } template requires(std::is_invocable_v) constexpr auto map_value(Func const& mapper) const& noexcept(std::is_nothrow_invocable_v) -> NonNull>> //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(mapper), static_cast(*get()))) }; } template requires(std::is_invocable_v) constexpr auto map_value(Func&& mapper) && noexcept(std::is_nothrow_invocable_v) -> NonNull>> //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(mapper), std::move(*get()))) }; } template requires(std::is_invocable_v) constexpr auto map_value(Func&& mapper) const&& noexcept(std::is_nothrow_invocable_v) -> NonNull>> //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(mapper), std::move(static_cast(*get())))) }; } template constexpr explicit operator NonNull() const noexcept requires(requires(T* ptr) { static_cast(ptr); }) { return cast(); } [[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()); } T* ptr_; }; /// Holds a *unique reference* that cannot be aliased, and cannot be null. template requires(!std::is_reference_v) struct Unique { using value_type = std::remove_cv_t>; using reference_type = UniqueRef; using const_reference_type = UniqueRef; using move_reference_type = UniqueMoveRef; using pointer_type = UniquePtr; using const_pointer_type = UniquePtr; constexpr static inline bool is_mutable = !std::is_same_v; constexpr static Unique try_new(pointer_type const&& ptr) { return { NonNull::try_new(ptr) }; } #ifdef DEBUG [[gnu::nonnull(1)]] #endif constexpr static Unique new_unchecked(pointer_type ptr) noexcept { #ifndef DEBUG _EO_ASSUME(ptr!=nullptr); #endif return { NonNull{ ptr } }; } constexpr Unique(std::convertible_to> 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() 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 m_ptr; }; #if 0 consteval bool uniq_check() noexcept { int ptr = 0; int* const ptr0 = &ptr; return Unique::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