Improved `NonNull` checking and added better mapping / pointer casting behaviour

Fortune for libexopt's current commit: Small blessing − 小吉
boxed_is_boxed_value
Avril 2 years ago
parent 67c553903b
commit 7447615b48
Signed by: flanchan
GPG Key ID: 284488987C31F630

2
.gitignore vendored

@ -1,4 +1,4 @@
obj/ obj/
lib*.so lib*.so*
lib*.a lib*.a
*.o *.o

@ -4,13 +4,16 @@ namespace exopt::types {
namespace either [[gnu::visibility("internal")]] { namespace either [[gnu::visibility("internal")]] {
//TODO: A version with std::shared_ptr<T> instead //TODO: A version with std::shared_ptr<T> instead
template<typename T>//, typename P = T*> template<typename T>//, typename P = T*>
class Cow final { class Cow {
using var_t = std::variant<pointer_type // Borrowed using var_t = std::variant<pointer_type // Borrowed
, value_type>; // Owned , value_type>; // Owned
public: public:
//TODO: Use pointer traits of P to deduce `pointer_type`, so we can have P be a shared_ptr too. Or perhaps, a specialisation for `Cow<std::shared_ptr<T>>` would be better //TODO: Use pointer traits of P to deduce `pointer_type`, so we can have P be a shared_ptr too. Or perhaps, a specialisation for `Cow<std::shared_ptr<T>>` would be better
using value_type = std::remove_cvref_t<T>; using value_type = std::remove_cvref_t<T>;
constexpr static inline bool Writeable = std::is_copy_constructible_v<value_type>;
constexpr static inline bool NTWriteable = std::is_nothrow_copy_constructible_v<value_type>;
using reference_type = value_type &; using reference_type = value_type &;
using const_reference_type = value_type const&; using const_reference_type = value_type const&;
using move_reference_type = value_type &&; using move_reference_type = value_type &&;
@ -38,7 +41,6 @@ namespace exopt::types {
return { *pref }; return { *pref };
else return { nullptr }; else return { nullptr };
} }
//TODO: into_owned(){, const}, to_owned(){, const}{&, &&}, etc..
//TODO: Operator overloads for access to `value_type`: operator*, operator->, etc. //TODO: Operator overloads for access to `value_type`: operator*, operator->, etc.
constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v<value_type>) { constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v<value_type>) {
@ -59,46 +61,82 @@ namespace exopt::types {
constexpr ~Cow() noexcept(std::is_nothrow_destructible_v<value_type>) = default; constexpr ~Cow() noexcept(std::is_nothrow_destructible_v<value_type>) = default;
constexpr move_reference_type as_ref() && { constexpr move_reference_type as_ref() && {
std::visit([](auto&& arg) -> move_reference_type { return make_owned();
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type)
return std::move(arg);
else return std::move(*arg);
}, m_value);
} }
constexpr reference_type as_ref() & { constexpr reference_type as_ref() & {
std::visit([](auto& arg) -> reference_type { return make_owned();
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type)
return std::forward<decltype(arg)>(arg);
else return *arg;
}, m_value);
} }
constexpr const_reference_type as_ref() const { constexpr const_reference_type as_ref() const {
std::visit([](const auto& arg) -> const_reference_type { return std::visit([](const auto& arg) -> const_reference_type {
using U = std::remove_cvref_t<decltype(arg)>; using U = std::remove_cvref_t<decltype(arg)>>;
if constexpr(std::is_same_v<U, value_type) if constexpr(std::is_same_v<U, value_type)
return std::forward<decltype(arg)>(arg); return std::forward<decltype(arg)>(arg);
else return *arg; else return *arg;
}, m_value); }, m_value);
} }
//TODO: ^ is_empty() null-checks for `*arg`. //TODO: ^ UNLIKELY(is_empty()) null-checks for `*arg`.
constexpr const_pointer_type get() const noexcept { constexpr const_pointer_type get() const noexcept {
std::visit([](const auto& arg) -> const_pointer_type { return std::visit([](const auto& arg) -> const_pointer_type {
using U = std::remove_cvref_t<decltype(arg)>; using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type) if constexpr(std::is_same_v<U, value_type>)
return std::addressof(arg); return std::addressof(arg);
else return arg; else return arg;
}, m_value); }, m_value);
} }
constexpr pointer_type get() noexcept { constexpr pointer_type get() noexcept {
std::visit([](auto& arg) -> pointer_type { return std::addressof(make_owned());
/*
return std::visit([](auto& arg) -> pointer_type {
using U = std::remove_cvref_t<decltype(arg)>; using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type) if constexpr(std::is_same_v<U, value_type>)
return std::addressof(arg); return std::addressof(arg);
else return arg; else return arg;
}, m_value); }, m_value);*/
}
constexpr reference_type make_owned() & noexcept(NTWriteable) requires(Writeable) {
if(const auto* ct_from = std::visit([&m_value](auto& arg) -> auto* {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, pointer_type>) return arg;
else return nullptr;
//if constexpr(std::is_same_v<U, value_type>) return false;
//else if(auto * refp = std::get_if<pointer_type>(m_value))
}, m_value)) return m_value.emplace<value_type>(*ct_from); //XXX: Do we need to create a temporary here for creating invariants by reading the old invariant?
}
constexpr move_reference_type make_owned() && noexcept(NTWriteable) requires(Writeable) {
if(const auto* ct_from = std::visit([&m_value](auto&& arg) -> auto* {
using U = std::remove_cvref_t<decltype(arg)>;
//TODO: I don't think this works the way I want it to... We'll need to emplace inside this function I think, since the moved temporary has been moved. It might not matter though.
if constexpr(std::is_same_v<U, pointer_type>) return arg;
else return nullptr;
//if constexpr(std::is_same_v<U, value_type>) return false;
//else if(auto * refp = std::get_if<pointer_type>(m_value))
}, std::move(m_value))) return std::move(m_value.emplace<value_type>(*ct_from)); //XXX: Do we need to create a temporary here for creating invariants by reading the old invariant?
}
//These commented out visits modify the pointee of the borrowed invariant. THIS IS NOT WHAT WE WANT. Instead, we emplace a copy and then copy it again (possibly elided: &) or return it as an rvalue reference (elided: &&)
constexpr value_type into_owned() & noexcept(NTWriteable) requires(Writeable) {
return make_owned();
/*return std::visit([](auto&& arg) -> move_reference_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type>)
return std::move(arg);
else return std::move(*arg);
}, std::move(m_value));*/
}
constexpr move_reference_type into_owned() && noexcept(NTWriteable) requires(Writeable) {
return make_owned();
/*return std::move(std::visit([](auto&& arg) -> move_reference_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type>)
return std::move(arg);
else return std::move(*arg);
}, std::move(m_value)));*/
} }
protected: protected:
constexpr bool is_empty() const noexcept { constexpr bool is_empty() const noexcept {
@ -114,7 +152,7 @@ namespace exopt::types {
}; };
template<typename T> template<typename T>
class Cow<std::shared_ptr<T>> final { class Cow<std::shared_ptr<T>> {
using var_t = std::variant< using var_t = std::variant<
std::weak_ptr<value_type> // Borrowed std::weak_ptr<value_type> // Borrowed
, std::shared_ptr<value_type>>; // Owned , std::shared_ptr<value_type>>; // Owned

@ -1,5 +1,7 @@
#pragma once #pragma once
#include <utility>
#include <functional>
#include <concepts> #include <concepts>
#include <bit> #include <bit>
#include <stdexcept> #include <stdexcept>
@ -138,6 +140,12 @@ namespace exopt::ptr {
template<typename T> requires(!std::is_reference_v<T>) template<typename T> requires(!std::is_reference_v<T>)
struct NonNull final { 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 constexpr NonNull(T& ref) noexcept
: ptr_(std::addressof(ref)) {} : ptr_(std::addressof(ref)) {}
[[gnu::nonnull(1, 2)]] [[gnu::nonnull(1, 2)]]
@ -150,15 +158,7 @@ namespace exopt::ptr {
constexpr ~NonNull() noexcept = default; constexpr ~NonNull() noexcept = default;
constexpr static NonNull<T> try_new(T* ptr) { constexpr static NonNull<T> try_new(T* ptr) {
constexpr auto _throw = [] [[noreturn, gnu::noinline, gnu::cold]] () { if(!ptr) _throw_null();
if consteval {
throw NullException{};
} else {
throw std::runtime_error(NullException{}.message());
}
//throw error::as_runtime_error(error::comptime_fail<NullException>());
};
if(!ptr) _throw();
return NonNull<T>{ptr}; return NonNull<T>{ptr};
} }
#ifdef DEBUG #ifdef DEBUG
@ -176,16 +176,51 @@ namespace exopt::ptr {
constexpr friend bool operator!=(std::nullptr_t, const NonNull&) noexcept { return true; } 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 bool operator!=(const NonNull&, std::nullptr_t) noexcept { return true; }
constexpr friend auto operator<=>(const NonNull& a, const NonNull& b) noexcept { return a.ptr_ <=> b.ptr_; } 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.ptr_ <=> b; } 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.ptr_); } 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]] [[gnu::returns_nonnull]]
constexpr T* get() const noexcept { return ptr_; } constexpr T* get() const noexcept { return ptr_; }
/*
[[gnu::returns_nonnull]]
constexpr T* const&& get() const&& noexcept { return std::move(ptr_); }
*/
[[gnu::returns_nonnull]] [[gnu::returns_nonnull]]
constexpr operator T*() const noexcept { return ptr_; } constexpr operator T*() const noexcept { return ptr_; }
@ -199,6 +234,15 @@ namespace exopt::ptr {
[[gnu::returns_nonnull]] [[gnu::returns_nonnull]]
constexpr const T* operator->() const noexcept { return ptr_; } constexpr const T* operator->() const noexcept { return ptr_; }
private: 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_; T* ptr_;
}; };
} }

Loading…
Cancel
Save