diff --git a/.gitignore b/.gitignore index f30e0fb..a23e8d9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ obj/ -lib*.so +lib*.so* lib*.a *.o diff --git a/include/either.hh b/include/either.hh index 3c7979b..3e7a290 100644 --- a/include/either.hh +++ b/include/either.hh @@ -4,13 +4,16 @@ namespace exopt::types { namespace either [[gnu::visibility("internal")]] { //TODO: A version with std::shared_ptr instead template//, typename P = T*> - class Cow final { + class Cow { using var_t = std::variant; // Owned 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>` would be better using value_type = std::remove_cvref_t; + constexpr static inline bool Writeable = std::is_copy_constructible_v; + constexpr static inline bool NTWriteable = std::is_nothrow_copy_constructible_v; + using reference_type = value_type &; using const_reference_type = value_type const&; using move_reference_type = value_type &&; @@ -38,7 +41,6 @@ namespace exopt::types { return { *pref }; else return { nullptr }; } - //TODO: into_owned(){, const}, to_owned(){, const}{&, &&}, etc.. //TODO: Operator overloads for access to `value_type`: operator*, operator->, etc. constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v) { @@ -59,46 +61,82 @@ namespace exopt::types { constexpr ~Cow() noexcept(std::is_nothrow_destructible_v) = default; constexpr move_reference_type as_ref() && { - std::visit([](auto&& arg) -> move_reference_type { - using U = std::remove_cvref_t; - if constexpr(std::is_same_v reference_type { - using U = std::remove_cvref_t; - if constexpr(std::is_same_v(arg); - else return *arg; - }, m_value); + return make_owned(); } constexpr const_reference_type as_ref() const { - std::visit([](const auto& arg) -> const_reference_type { - using U = std::remove_cvref_t; + return std::visit([](const auto& arg) -> const_reference_type { + using U = std::remove_cvref_t>; if constexpr(std::is_same_v(arg); else return *arg; }, m_value); } - //TODO: ^ is_empty() null-checks for `*arg`. + //TODO: ^ UNLIKELY(is_empty()) null-checks for `*arg`. 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; - if constexpr(std::is_same_v) return std::addressof(arg); else return arg; }, m_value); } 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; - if constexpr(std::is_same_v) return std::addressof(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; + + if constexpr(std::is_same_v) return arg; + else return nullptr; + //if constexpr(std::is_same_v) return false; + //else if(auto * refp = std::get_if(m_value)) + + }, m_value)) return m_value.emplace(*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; + + //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) return arg; + else return nullptr; + //if constexpr(std::is_same_v) return false; + //else if(auto * refp = std::get_if(m_value)) + + }, std::move(m_value))) return std::move(m_value.emplace(*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; + if constexpr(std::is_same_v) + 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; + if constexpr(std::is_same_v) + return std::move(arg); + else return std::move(*arg); + }, std::move(m_value)));*/ } protected: constexpr bool is_empty() const noexcept { @@ -114,7 +152,7 @@ namespace exopt::types { }; template - class Cow> final { + class Cow> { using var_t = std::variant< std::weak_ptr // Borrowed , std::shared_ptr>; // Owned diff --git a/include/pointer.h b/include/pointer.h index 7f4eaaf..992b296 100644 --- a/include/pointer.h +++ b/include/pointer.h @@ -1,5 +1,7 @@ #pragma once +#include +#include #include #include #include @@ -138,6 +140,12 @@ namespace exopt::ptr { template requires(!std::is_reference_v) struct NonNull final { + + 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)]] @@ -150,15 +158,7 @@ namespace exopt::ptr { constexpr ~NonNull() noexcept = default; constexpr static NonNull try_new(T* ptr) { - constexpr auto _throw = [] [[noreturn, gnu::noinline, gnu::cold]] () { - if consteval { - throw NullException{}; - } else { - throw std::runtime_error(NullException{}.message()); - } - //throw error::as_runtime_error(error::comptime_fail()); - }; - if(!ptr) _throw(); + if(!ptr) _throw_null(); return NonNull{ptr}; } #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!=(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, T* b) noexcept { return (!b) ? (true<=>false) : a.ptr_ <=> b; } - constexpr friend auto operator<=>(T* a, const NonNull& b) noexcept { return (!a) ? (false<=>true) : (a <=> 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.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 T* const&& get() const&& noexcept { return std::move(ptr_); } -*/ + [[gnu::returns_nonnull]] constexpr operator T*() const noexcept { return ptr_; } @@ -199,6 +234,15 @@ namespace exopt::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()); + } T* ptr_; }; }