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

249 lines
10 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 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"; }
};
template<typename T> requires(!std::is_reference_v<T>)
struct NonNull final {
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(!ptr) _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 void _throw_null() {
if consteval {
throw NullException{};
} else {
throw std::runtime_error(NullException{}.message());
}
//throw error::as_runtime_error(error::comptime_fail<NullException>());
}
T* ptr_;
};
}