shared_ptr.hpp: further fixes

Improved is_same_ptr() trait (added third "maybe" category).
Fallback to pseudo-runtime check in certain cases (if "maybe").
Fix single_ptr moving assignment operator.
This commit is contained in:
Nekotekina 2021-04-08 18:45:43 +03:00
commit 8c5f2b249c
2 changed files with 124 additions and 53 deletions

View file

@ -25,16 +25,23 @@ namespace stx
static thread_local const static thread_local const
#endif #endif
fake_t<std::remove_cv_t<T>> sample{}; fake_t<std::remove_cv_t<T>> sample{};
template <typename From, typename To, typename = void>
struct can_static_cast : std::false_type {};
template <typename From, typename To>
struct can_static_cast<From, To, std::void_t<decltype(static_cast<To>(std::declval<From>()))>> : std::true_type {};
} }
// Classify compile-time information available for pointers
enum class same_ptr
{
no,
yes,
maybe
};
template <typename X, typename Y> template <typename X, typename Y>
constexpr bool is_same_ptr_test(const volatile Y*, ...)
{
// Can't sample abstract class
return false;
}
template <typename X, typename Y, std::enable_if_t<!std::is_abstract_v<Y>, int> = 0>
constexpr bool is_same_ptr_test(const volatile Y* ptr = std::addressof(detail::sample<Y>.data)) constexpr bool is_same_ptr_test(const volatile Y* ptr = std::addressof(detail::sample<Y>.data))
{ {
return static_cast<const volatile X*>(ptr) == static_cast<const volatile void*>(ptr); return static_cast<const volatile X*>(ptr) == static_cast<const volatile void*>(ptr);
@ -42,26 +49,26 @@ namespace stx
// Checks whether the cast between two types is the same pointer // Checks whether the cast between two types is the same pointer
template <typename T, typename U> template <typename T, typename U>
constexpr bool is_same_ptr() noexcept constexpr same_ptr is_same_ptr() noexcept
{ {
if constexpr (std::is_void_v<T> || std::is_void_v<U> || std::is_same_v<T, U>) if constexpr (std::is_void_v<T> || std::is_void_v<U> || std::is_same_v<std::remove_cv_t<T>, std::remove_cv_t<U>>)
{ {
return true; return same_ptr::yes;
} }
else if constexpr (std::is_convertible_v<U*, T*>) else if constexpr (detail::can_static_cast<U*, T*>::value && !std::is_abstract_v<U>)
{ {
return is_same_ptr_test<T, U>(); return is_same_ptr_test<T, U>() ? same_ptr::yes : same_ptr::no;
} }
else if constexpr (std::is_convertible_v<T*, U*>) else if constexpr (detail::can_static_cast<T*, U*>::value && !std::is_abstract_v<T>)
{ {
return is_same_ptr_test<U, T>(); return is_same_ptr_test<U, T>() ? same_ptr::yes : same_ptr::no;
} }
return !std::is_class_v<T> && !std::is_class_v<U> && !std::is_union_v<T> && !std::is_union_v<U>; return same_ptr::maybe;
} }
template <typename T, typename U> template <typename T, typename U>
constexpr bool is_same_ptr_cast_v = std::is_convertible_v<U*, T*> && is_same_ptr<T, U>(); constexpr same_ptr is_same_ptr_cast_v = std::is_convertible_v<U*, T*> ? is_same_ptr<T, U>() : same_ptr::no;
template <typename T> template <typename T>
class single_ptr; class single_ptr;
@ -166,10 +173,13 @@ namespace stx
r.m_ptr = nullptr; r.m_ptr = nullptr;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
single_ptr(single_ptr<U>&& r) noexcept single_ptr(single_ptr<U>&& r) noexcept
: m_ptr(r.m_ptr)
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
m_ptr = r.m_ptr;
r.m_ptr = nullptr; r.m_ptr = nullptr;
} }
@ -182,16 +192,17 @@ namespace stx
single_ptr& operator=(single_ptr&& r) noexcept single_ptr& operator=(single_ptr&& r) noexcept
{ {
m_ptr = r.m_ptr; single_ptr(std::move(r)).swap(*this);
r.m_ptr = nullptr;
return *this; return *this;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
single_ptr& operator=(single_ptr<U>&& r) noexcept single_ptr& operator=(single_ptr<U>&& r) noexcept
{ {
m_ptr = r.m_ptr; if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
r.m_ptr = nullptr; ensure(is_same_ptr_test<T, U>(r.m_ptr));
single_ptr(std::move(r)).swap(*this);
return *this; return *this;
} }
@ -259,9 +270,12 @@ namespace stx
} }
// "Moving" "static cast" // "Moving" "static cast"
template <typename U, typename = decltype(static_cast<U*>(std::declval<T*>())), typename = std::enable_if_t<is_same_ptr<U, T>()>> template <typename U, typename = decltype(static_cast<U*>(std::declval<T*>())), typename = std::enable_if_t<is_same_ptr<U, T>() != same_ptr::no>>
explicit operator single_ptr<U>() && noexcept explicit operator single_ptr<U>() && noexcept
{ {
if constexpr (is_same_ptr<U, T>() == same_ptr::maybe)
ensure(is_same_ptr_test<U, T>(m_ptr));
single_ptr<U> r; single_ptr<U> r;
r.m_ptr = static_cast<decltype(r.m_ptr)>(std::exchange(m_ptr, nullptr)); r.m_ptr = static_cast<decltype(r.m_ptr)>(std::exchange(m_ptr, nullptr));
return r; return r;
@ -424,10 +438,13 @@ namespace stx
d()->refs++; d()->refs++;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
shared_ptr(const shared_ptr<U>& r) noexcept shared_ptr(const shared_ptr<U>& r) noexcept
: m_ptr(r.m_ptr)
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
m_ptr = r.m_ptr;
if (m_ptr) if (m_ptr)
d()->refs++; d()->refs++;
} }
@ -438,17 +455,23 @@ namespace stx
r.m_ptr = nullptr; r.m_ptr = nullptr;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
shared_ptr(shared_ptr<U>&& r) noexcept shared_ptr(shared_ptr<U>&& r) noexcept
: m_ptr(r.m_ptr)
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
m_ptr = r.m_ptr;
r.m_ptr = nullptr; r.m_ptr = nullptr;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
shared_ptr(single_ptr<U>&& r) noexcept shared_ptr(single_ptr<U>&& r) noexcept
: m_ptr(r.m_ptr)
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
m_ptr = r.m_ptr;
r.m_ptr = nullptr; r.m_ptr = nullptr;
} }
@ -463,9 +486,12 @@ namespace stx
return *this; return *this;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
shared_ptr& operator=(const shared_ptr<U>& r) noexcept shared_ptr& operator=(const shared_ptr<U>& r) noexcept
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
shared_ptr(r).swap(*this); shared_ptr(r).swap(*this);
return *this; return *this;
} }
@ -476,16 +502,22 @@ namespace stx
return *this; return *this;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
shared_ptr& operator=(shared_ptr<U>&& r) noexcept shared_ptr& operator=(shared_ptr<U>&& r) noexcept
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
shared_ptr(std::move(r)).swap(*this); shared_ptr(std::move(r)).swap(*this);
return *this; return *this;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
shared_ptr& operator=(single_ptr<U>&& r) noexcept shared_ptr& operator=(single_ptr<U>&& r) noexcept
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
shared_ptr(std::move(r)).swap(*this); shared_ptr(std::move(r)).swap(*this);
return *this; return *this;
} }
@ -503,9 +535,12 @@ namespace stx
} }
// Converts to unique (single) ptr if reference is 1, otherwise returns null. Nullifies self. // Converts to unique (single) ptr if reference is 1, otherwise returns null. Nullifies self.
template <typename U, typename = decltype(static_cast<U*>(std::declval<T*>())), typename = std::enable_if_t<is_same_ptr<U, T>()>> template <typename U, typename = decltype(static_cast<U*>(std::declval<T*>())), typename = std::enable_if_t<is_same_ptr<U, T>() != same_ptr::no>>
explicit operator single_ptr<U>() && noexcept explicit operator single_ptr<U>() && noexcept
{ {
if constexpr (is_same_ptr<U, T>() == same_ptr::maybe)
ensure(is_same_ptr_test<U, T>(m_ptr));
const auto o = d(); const auto o = d();
if (m_ptr && !--o->refs) if (m_ptr && !--o->refs)
@ -588,9 +623,12 @@ namespace stx
} }
// Basic "static cast" support // Basic "static cast" support
template <typename U, typename = decltype(static_cast<U*>(std::declval<T*>())), typename = std::enable_if_t<is_same_ptr<U, T>()>> template <typename U, typename = decltype(static_cast<U*>(std::declval<T*>())), typename = std::enable_if_t<is_same_ptr<U, T>() != same_ptr::no>>
explicit operator shared_ptr<U>() const& noexcept explicit operator shared_ptr<U>() const& noexcept
{ {
if constexpr (is_same_ptr<U, T>() == same_ptr::maybe)
ensure(is_same_ptr_test<U, T>(m_ptr));
if (m_ptr) if (m_ptr)
{ {
d()->refs++; d()->refs++;
@ -602,9 +640,12 @@ namespace stx
} }
// "Moving" "static cast" // "Moving" "static cast"
template <typename U, typename = decltype(static_cast<U*>(std::declval<T*>())), typename = std::enable_if_t<is_same_ptr<U, T>()>> template <typename U, typename = decltype(static_cast<U*>(std::declval<T*>())), typename = std::enable_if_t<is_same_ptr<U, T>() != same_ptr::no>>
explicit operator shared_ptr<U>() && noexcept explicit operator shared_ptr<U>() && noexcept
{ {
if constexpr (is_same_ptr<U, T>() == same_ptr::maybe)
ensure(is_same_ptr_test<U, T>(m_ptr));
shared_ptr<U> r; shared_ptr<U> r;
r.m_ptr = static_cast<decltype(r.m_ptr)>(std::exchange(m_ptr, nullptr)); r.m_ptr = static_cast<decltype(r.m_ptr)>(std::exchange(m_ptr, nullptr));
return r; return r;
@ -660,29 +701,38 @@ namespace stx
d()->refs.raw() += c_ref_mask; d()->refs.raw() += c_ref_mask;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
atomic_ptr(const shared_ptr<U>& r) noexcept atomic_ptr(const shared_ptr<U>& r) noexcept
: m_val(reinterpret_cast<uptr>(r.m_ptr) << c_ref_size)
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
// Obtain a ref + as many refs as an atomic_ptr can additionally reference // Obtain a ref + as many refs as an atomic_ptr can additionally reference
m_val = reinterpret_cast<uptr>(r.m_ptr) << c_ref_size;
if (m_val) if (m_val)
d()->refs += c_ref_mask + 1; d()->refs += c_ref_mask + 1;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
atomic_ptr(shared_ptr<U>&& r) noexcept atomic_ptr(shared_ptr<U>&& r) noexcept
: m_val(reinterpret_cast<uptr>(r.m_ptr) << c_ref_size)
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
m_val = reinterpret_cast<uptr>(r.m_ptr) << c_ref_size;
r.m_ptr = nullptr; r.m_ptr = nullptr;
if (m_val) if (m_val)
d()->refs += c_ref_mask; d()->refs += c_ref_mask;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
atomic_ptr(single_ptr<U>&& r) noexcept atomic_ptr(single_ptr<U>&& r) noexcept
: m_val(reinterpret_cast<uptr>(r.m_ptr) << c_ref_size)
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
m_val = reinterpret_cast<uptr>(r.m_ptr) << c_ref_size;
r.m_ptr = nullptr; r.m_ptr = nullptr;
if (m_val) if (m_val)
@ -711,23 +761,32 @@ namespace stx
return *this; return *this;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
atomic_ptr& operator=(const shared_ptr<U>& r) noexcept atomic_ptr& operator=(const shared_ptr<U>& r) noexcept
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
store(r); store(r);
return *this; return *this;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
atomic_ptr& operator=(shared_ptr<U>&& r) noexcept atomic_ptr& operator=(shared_ptr<U>&& r) noexcept
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
store(std::move(r)); store(std::move(r));
return *this; return *this;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
atomic_ptr& operator=(single_ptr<U>&& r) noexcept atomic_ptr& operator=(single_ptr<U>&& r) noexcept
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(r.m_ptr));
store(std::move(r)); store(std::move(r));
return *this; return *this;
} }
@ -988,9 +1047,12 @@ namespace stx
} }
// Unoptimized // Unoptimized
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
shared_type compare_and_swap(const shared_ptr<U>& cmp, shared_type exch) shared_type compare_and_swap(const shared_ptr<U>& cmp, shared_type exch)
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(cmp.m_ptr));
shared_type old = cmp; shared_type old = cmp;
if (compare_exchange(old, std::move(exch))) if (compare_exchange(old, std::move(exch)))
@ -1004,9 +1066,12 @@ namespace stx
} }
// More lightweight than compare_exchange // More lightweight than compare_exchange
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
bool compare_and_swap_test(const shared_ptr<U>& cmp, shared_type exch) bool compare_and_swap_test(const shared_ptr<U>& cmp, shared_type exch)
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(cmp.m_ptr));
const uptr _old = reinterpret_cast<uptr>(cmp.m_ptr); const uptr _old = reinterpret_cast<uptr>(cmp.m_ptr);
const uptr _new = reinterpret_cast<uptr>(exch.m_ptr); const uptr _new = reinterpret_cast<uptr>(exch.m_ptr);
@ -1043,9 +1108,12 @@ namespace stx
} }
// Unoptimized // Unoptimized
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
shared_type compare_and_swap(const single_ptr<U>& cmp, shared_type exch) shared_type compare_and_swap(const single_ptr<U>& cmp, shared_type exch)
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(cmp.m_ptr));
shared_type old = cmp; shared_type old = cmp;
if (compare_exchange(old, std::move(exch))) if (compare_exchange(old, std::move(exch)))
@ -1059,9 +1127,12 @@ namespace stx
} }
// Supplementary // Supplementary
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
bool compare_and_swap_test(const single_ptr<U>& cmp, shared_type exch) bool compare_and_swap_test(const single_ptr<U>& cmp, shared_type exch)
{ {
if constexpr (is_same_ptr<T, U>() == same_ptr::maybe)
ensure(is_same_ptr_test<T, U>(cmp.m_ptr));
return compare_and_swap_test(reinterpret_cast<const shared_ptr<U>&>(cmp), std::move(exch)); return compare_and_swap_test(reinterpret_cast<const shared_ptr<U>&>(cmp), std::move(exch));
} }
@ -1111,13 +1182,13 @@ namespace stx
return m_val != 0; return m_val != 0;
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
bool is_equal(const shared_ptr<U>& r) const noexcept bool is_equal(const shared_ptr<U>& r) const noexcept
{ {
return observe() == r.get(); return observe() == r.get();
} }
template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U>>> template <typename U, typename = std::enable_if_t<is_same_ptr_cast_v<T, U> != same_ptr::no>>
bool is_equal(const single_ptr<U>& r) const noexcept bool is_equal(const single_ptr<U>& r) const noexcept
{ {
return observe() == r.get(); return observe() == r.get();

View file

@ -255,7 +255,7 @@ namespace stx
else else
{ {
static_assert(sizeof(As) > 0); static_assert(sizeof(As) > 0);
static_assert(is_same_ptr<T, As>()); static_assert(is_same_ptr<T, As>() == same_ptr::yes); // TODO
return type_counter<Info>::template dyn_type<T, As>.index(); return type_counter<Info>::template dyn_type<T, As>.index();
} }
} }
@ -274,7 +274,7 @@ namespace stx
ATTR_PURE inline const Info& typedata() noexcept ATTR_PURE inline const Info& typedata() noexcept
{ {
static_assert(sizeof(T) > 0 && sizeof(As) > 0); static_assert(sizeof(T) > 0 && sizeof(As) > 0);
static_assert(is_same_ptr<T, As>()); static_assert(is_same_ptr<T, As>() == same_ptr::yes); // TODO
return type_counter<Info>::template dyn_type<T, As>; return type_counter<Info>::template dyn_type<T, As>;
} }