# 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 > > ;
}