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.
226 lines
9.1 KiB
226 lines
9.1 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 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>
|
|
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;
|
|
};
|
|
}
|
|
|
|
/// Manually lifetime managed Cow<T>
|
|
using either::Cow;
|
|
|
|
/// RC-lifetime managed Cow<T>
|
|
template<typename T>
|
|
using RcCow = Cow<std::shared_ptr<T>>;
|
|
}
|