#pragma once namespace exopt::types { #if 0 //XXX: Eh, this trait design is NOT GOOD. Figure out a better one namespace traits { template struct impl_clone { typedef T type; constexpr static T clone(T const& v) const noexcept(std::is_nothrow_copy_constructible_v) { return v; } }; template<> struct impl_clone {}; template struct clone; template<> struct clone : impl_clone {}; template struct clone : impl_clone, T, void> > {}; template struct clone : impl_clone) -> std::same_as; }) , T , void > > { constexpr static T clone(T const& }; } template concept Clone = requires(T const& self) { typename traits::clone::type; { traits::clone::clone(self) } noexcept(std::is_nothrow_copy_constructible_v) -> std::same_as; } or requires(T const& self) { { self.clone() } noexcept(std::is_nothrow_copy_constructible_v) -> std::same_as; }; #endif namespace either [[gnu::visibility("internal")]] { //TODO: A version with std::shared_ptr instead template//, typename P = T*> 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 &&; using pointer_type = value_type *; using const_pointer_type = value_type const*; constexpr Cow(std::add_rvalue_reference_t value) noexcept(std::is_nothrow_constructible_v) : m_value{std::move(value)} {} constexpr explicit(std::is_convertible_v, pointer_type>) Cow(pointer_type p) noexcept : m_value{p} {} constexpr explicit Cow(std::nullptr_t) noexcept : Cow(std::move(static_cast(nullptr))) {} constexpr Cow(Cow const& copy) noexcept : m_value(copy.get()) {} constexpr Cow(Cow&& move) noexcept(std::is_nothrow_move_constructible_v) : m_value(std::move(move.m_value)) { move.clear(); /* XXX: Is this needed? I think it is, to ensure that Cow 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) requires(std::is_copy_constructible_v) { 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) { if(this != std::addressof(move)) { if(__builtin_expect(move.is_empty(), false)) clear(); else { m_value.emplace(std::move(move).as_ref()) move.clear(); } } return *this; } constexpr Cow(Cow const& copy) noexcept(std::is_nothrow_destructible_v) { if(this != std::addressof(copy)) { m_value.emplace(copy.get()); } return *this; } constexpr ~Cow() noexcept(std::is_nothrow_destructible_v) = 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>; if constexpr(std::is_same_v(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; if constexpr(std::is_same_v) 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; if constexpr(std::is_same_v) 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; 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 { if(const const_pointer_type* p_ref = std::get_if(&m_value)) if(!p_ref) return true; return false; } constexpr void clear() noexcept(std::is_nothrow_destructible_v) { m_value.emplace(nullptr); } private: var_t m_value; }; template class Cow> { using var_t = std::variant< std::weak_ptr // Borrowed , std::shared_ptr>; // Owned public: using value_type = std::remove_cvref_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'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 using either::Cow; /// RC-lifetime managed Cow template using RcCow = Cow>; }