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/either.hh

258 lines
9.7 KiB

#pragma once
namespace exopt::types {
#if 0
//XXX: Eh, this trait design is NOT GOOD. Figure out a better one
namespace traits {
template<typename T>
struct impl_clone {
typedef T type;
constexpr static T clone(T const& v) const noexcept(std::is_nothrow_copy_constructible_v<type>) { return v; }
};
template<>
struct impl_clone<void> {};
template<typename> struct clone;
template<>
struct clone : impl_clone<void> {};
template<typename T>
struct clone : impl_clone<std::conditional_t< std::is_copy_constructible_v<T>, T, void> > {};
template<typename T>
struct clone : impl_clone<std::conditional_t
< requires(requires(T const& self) {
{ self.clone() } noexcept(std::is_nothrow_copy_constructible_v<T>)
-> std::same_as<T>;
})
, T
, void
> > {
constexpr static T clone(T const&
};
}
template<typename T>
concept Clone = requires(T const& self) {
typename traits::clone<T>::type;
{ traits::clone<T>::clone(self) } noexcept(std::is_nothrow_copy_constructible_v<T>) -> std::same_as<T>;
} or requires(T const& self) {
{ self.clone() } noexcept(std::is_nothrow_copy_constructible_v<T>) -> std::same_as<T>;
};
#endif
namespace either [[gnu::visibility("internal")]] {
//TODO: A version with std::shared_ptr<T> instead
template<typename T>//, typename P = T*>
class Cow {
using var_t = std::variant<pointer_type // Borrowed
, value_type>; // 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<std::shared_ptr<T>>` would be better
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 const_reference_type = value_type const&;
using move_reference_type = value_type &&;
using pointer_type = value_type *;
using const_pointer_type = value_type const*;
constexpr Cow(std::add_rvalue_reference_t<T> value) noexcept(std::is_nothrow_constructible_v<value_type, decltype(value)>)
: m_value{std::move(value)} {}
constexpr explicit(std::is_convertible_v<std::add_rvalue_reference<T>, pointer_type>)
Cow(pointer_type p) noexcept
: m_value{p} {}
constexpr explicit Cow(std::nullptr_t) noexcept
: Cow(std::move(static_cast<pointer_type>(nullptr))) {}
constexpr Cow(Cow const& copy) noexcept
: m_value(copy.get()) {}
constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v<value_type>)
: m_value(std::move(move.m_value)) { move.clear(); /* XXX: Is this needed? I think it is, to ensure that Cow<T> knows if it's been moved, even though std::variant already knows */ }
/// Return a newly copy-constructed owned object `T` from the referred (or held) value.
constexpr Cow clone() const noexcept(std::is_nothrow_copy_constructible_v<value_type>) requires(std::is_copy_constructible_v<T>)
{
if(const auto* pref = this->get())
return { *pref };
else return { nullptr };
}
//TODO: Operator overloads for access to `value_type`: operator*, operator->, etc.
constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v<value_type>) {
if(this != std::addressof(move)) {
if(__builtin_expect(move.is_empty(), false)) clear();
else {
m_value.emplace<value_type>(std::move(move).as_ref())
move.clear();
}
} return *this;
}
constexpr Cow(Cow const& copy) noexcept(std::is_nothrow_destructible_v<value_type>) {
if(this != std::addressof(copy)) {
m_value.emplace<pointer_type>(copy.get());
} return *this;
}
constexpr ~Cow() noexcept(std::is_nothrow_destructible_v<value_type>) = default;
constexpr move_reference_type as_ref() && {
return make_owned();
}
constexpr reference_type as_ref() & {
return make_owned();
}
constexpr const_reference_type as_ref() const {
return std::visit([](const auto& arg) -> const_reference_type {
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);
}
//TODO: ^ UNLIKELY(is_empty()) null-checks for `*arg`.
constexpr reference_type operator*() & noexcept {
return as_ref();
}
constexpr move_reference_type operator*() && noexcept {
return as_ref();
}
constexpr const_reference_type operator*() const noexcept {
return as_ref();
}
constexpr pointer_type operator->() noexcept {
return get();
}
constexpr const_pointer_type operator->() noexcept {
return get();
}
constexpr const_pointer_type get() const noexcept {
return std::visit([](const auto& arg) -> const_pointer_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type>)
return std::addressof(arg);
else return arg;
}, m_value);
}
constexpr pointer_type get() noexcept {
return std::addressof(make_owned());
/*
return std::visit([](auto& arg) -> pointer_type {
using U = std::remove_cvref_t<decltype(arg)>;
if constexpr(std::is_same_v<U, value_type>)
return std::addressof(arg);
else return arg;
}, 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:
constexpr bool is_empty() const noexcept {
if(const const_pointer_type* p_ref = std::get_if<pointer_type>(&m_value))
if(!p_ref) return true;
return false;
}
constexpr void clear() noexcept(std::is_nothrow_destructible_v<value_type>) {
m_value.emplace<pointer_type>(nullptr);
}
private:
var_t m_value;
};
//template<typename T>
//TODO: We want it to work: class Cow<std::unique_ptr<T>> : public Cow<T> {};
#if 0
template<typename T>
class Cow<std::shared_ptr<T>> {
using var_t = std::variant<
std::weak_ptr<value_type> // Borrowed
, std::shared_ptr<value_type>>; // Owned
public:
using value_type = std::remove_cvref_t<T>;
using reference_type = value_type &;
using const_reference_type = value_type const&;
using move_reference_type = value_type &&;
using pointer_type = value_type;
using const_pointer_type = value_type const*;
//TODO: AutoCow: See above comment about RC'd lifetimes with shallow coppies.
// How should we implement this... I think weak for borrowed and shared for owned is good, since we can still use shared_ptr's aliasing ctor to apply clone() (copy-construction) to the `value_type` directly while keeping the ref-count.
// XXX: Therfore, there should never be *any* strictly-aliasing variants both exposing the same `value_type`, despite *all* the variants managing the lifetime of the `value_type`. I think... Hnm... Can we get shared_ptr<T>'s aliasing constructor to *expose* a newly constructed `value_type` while managing the new one and the old one? XXX: Should we even manage the old one at all after a `clone()`? I actually don't think we should, since `clone()` should detach lifetime from the instance that it was called since it's a newly constructed (owned) object.
private:
var_t m_value;
};
#endif
}
/// Manually lifetime managed Cow<T>
using either::Cow;
/// RC-lifetime managed Cow<T>
template<typename T>
using RcCow = Cow<std::shared_ptr<T>>;
/// Boxed unique Cow<T>
template<typename T>
using BoxCow = Cow<std::unique_ptr<T>>;
}