diff options
-rw-r--r-- | compat/intrusive-ptr.hpp | 326 | ||||
-rw-r--r-- | compat/intrusive-ptr.inl | 8 | ||||
-rw-r--r-- | test/app.hpp | 1 | ||||
-rw-r--r-- | test/intrusive-ptr.cpp | 169 | ||||
-rw-r--r-- | test/main.cpp | 1 |
5 files changed, 505 insertions, 0 deletions
diff --git a/compat/intrusive-ptr.hpp b/compat/intrusive-ptr.hpp new file mode 100644 index 00000000..0d09a366 --- /dev/null +++ b/compat/intrusive-ptr.hpp @@ -0,0 +1,326 @@ +#pragma once +#include "compat/assert.hpp" +#include <compare> +#include <Corrade/Utility/Move.h> + +namespace floormat::iptr { struct non_atomic_u32_tag{}; } + +namespace floormat { + +template<typename Tag, typename T> class basic_iptr; +template<typename T> using local_iptr = basic_iptr<iptr::non_atomic_u32_tag, T>; + +} // namespace floormat + +namespace floormat::iptr { + +template<typename Tag, typename T> struct refcount_traits; +template<typename Tag, typename T> struct refcount_access; +template<typename Tag, typename T> struct refcount_ops; + +template<typename T> +struct refcount_traits<non_atomic_u32_tag, T> +{ + refcount_traits() = delete; + using counter_type = uint32_t; + using size_type = size_t; + using access_t = refcount_access<non_atomic_u32_tag, T>; +}; + +template<typename Tag, typename T> +struct refcount_ops +{ + refcount_ops() = delete; + + using traits = refcount_traits<Tag, T>; + using counter_type = typename traits::counter_type; + using size_type = typename traits::size_type; + using access_t = typename traits::access_t; + +#ifndef FM_NO_DEBUG + using Tref = T*&; +#else + using Tref = T*; +#endif + + static constexpr inline auto incr(T* ptr) noexcept -> size_type; + static constexpr inline auto decr(Tref ptr) noexcept -> size_type; + static constexpr inline auto count(T* ptr) noexcept -> size_type; + static constexpr inline void init_to_1(T* ptr) noexcept; +}; + +template<typename Tag, typename T> +struct refcount_access +{ + using counter_type = refcount_traits<Tag, T>::counter_type; + refcount_access() = delete; + + static constexpr auto access(T* ptr) noexcept -> counter_type&; + template<typename Y> static constexpr Y* checked_cast(const T* ptr) noexcept; // todo! +}; + +template<typename Tag, typename T> +constexpr auto refcount_ops<Tag, T>::incr(T* ptr) noexcept -> size_type +{ + fm_debug_assert(ptr != nullptr); + counter_type& ctr{access_t::access(ptr)}; + fm_debug_assert(size_type{ctr} > size_type{0}); + size_type ret{++ctr}; + return ret; +} + +template<typename Tag, typename T> +constexpr auto refcount_ops<Tag, T>::decr(Tref ptr) noexcept -> size_type +{ + static_assert(std::is_nothrow_destructible_v<T>); + + fm_debug_assert(ptr != nullptr); + counter_type& ctr{access_t::access(ptr)}; + fm_debug_assert(size_type{ctr} > size_type{0}); + size_type ret{--ctr}; + if (ret == size_type{0}) + { + delete ptr; + if constexpr(std::is_reference_v<Tref>) + ptr = reinterpret_cast<T*>((uintptr_t)-1); + } + return ret; +} + +template<typename Tag, typename T> +constexpr auto refcount_ops<Tag, T>::count(T* ptr) noexcept -> size_type +{ + if (!CORRADE_CONSTEVAL) + fm_debug_assert((void*)ptr != (void*)-1); + if (!ptr) [[unlikely]] + return 0; + counter_type& ctr{access_t::access(ptr)}; + fm_debug_assert(size_type{ctr} > size_type{0}); + size_type ret{ctr}; + return ret; +} + +template<typename Tag, typename T> +constexpr void refcount_ops<Tag, T>::init_to_1(T* ptr) noexcept +{ + fm_debug_assert(ptr != nullptr); + counter_type& ctr{access_t::access(ptr)}; + fm_debug_assert(size_type{ctr} == size_type{0}); + ctr = size_type{1}; +} + +template<typename Tag, typename T> +consteval bool check_traits() +{ + using traits = ::floormat::iptr::refcount_traits<Tag, T>; + using counter_type = typename traits::counter_type; + using size_type = typename traits::size_type; + using access_t = ::floormat::iptr::refcount_access<Tag, T>; + using ops_t = ::floormat::iptr::refcount_ops<Tag, T>; + + static_assert(requires (counter_type& ctr) { + requires std::is_arithmetic_v<size_type>; + requires std::is_unsigned_v<size_type>; + requires std::is_fundamental_v<size_type>; + requires noexcept(size_type{ctr}); + { size_t{size_type{ctr}} }; + { ctr = size_type{1} }; + { size_type{++ctr} }; + { size_type{--ctr} }; + requires noexcept(size_t{size_type{ctr}}); + requires noexcept(ctr = size_type{1}); + requires noexcept(size_type{++ctr}); + requires noexcept(size_type{--ctr}); + requires std::is_nothrow_destructible_v<T>; + requires sizeof(access_t) != 0; + requires sizeof(ops_t) != 0; + }); + + return true; +} + +} // namespace floormat::iptr + + + +// ----- macros ----- + +#define fm_template template<typename Tag, typename T> +#define fm_basic_iptr basic_iptr<Tag, T> + +#ifndef FM_IPTR_USE_CONSTRUCT_AT // todo! finish it +#define FM_IPTR_USE_CONSTRUCT_AT 0 +#endif + +// ----- basic_iptr ----- + +namespace floormat { + +template<typename Tag, typename T> +class basic_iptr final +{ + static_assert(!std::is_reference_v<T>); + static_assert(!std::is_const_v<T>); // todo, modify only refcount + + using traits = ::floormat::iptr::refcount_traits<Tag, T>; + using counter_type = typename traits::counter_type; + using size_type = typename traits::size_type; + using access_t = ::floormat::iptr::refcount_access<Tag, T>; + using ops_t = ::floormat::iptr::refcount_ops<Tag, T>; + static_assert(::floormat::iptr::check_traits<Tag, T>()); + + T* _ptr{nullptr}; + + // todo use std::construct_at as it has a constexpr exception. + // ...but it requires <memory> :( — maybe use an ifdef? + +public: + constexpr basic_iptr() noexcept; + constexpr basic_iptr(std::nullptr_t) noexcept; + constexpr basic_iptr& operator=(std::nullptr_t) noexcept; + explicit constexpr basic_iptr(T* ptr) noexcept; + constexpr basic_iptr(basic_iptr&& other) noexcept; + constexpr basic_iptr(const basic_iptr& other) noexcept; + constexpr basic_iptr& operator=(basic_iptr&& other) noexcept; + constexpr basic_iptr& operator=(const basic_iptr& other) noexcept; + constexpr ~basic_iptr() noexcept; + + template<typename... Ts> requires std::is_constructible_v<T, Ts&&...> + constexpr basic_iptr(InPlaceInitT, Ts&&... args) // NOLINT(*-missing-std-forward) + noexcept(std::is_nothrow_constructible_v<T, Ts&&...>); + + constexpr inline void reset() noexcept; + constexpr void reset(T* ptr) noexcept; // todo casts + constexpr void swap(basic_iptr& other) noexcept; + constexpr T* get() const noexcept; + constexpr inline T& operator*() const noexcept; + constexpr inline T* operator->() const noexcept; + + constexpr size_type use_count() const noexcept; + explicit constexpr operator bool() const noexcept; + + template<typename TAG, typename TYPE> + friend constexpr bool operator==(const basic_iptr<TAG, TYPE>& a, const basic_iptr<TAG, TYPE>& b) noexcept; + + template<typename TAG, typename TYPE> + friend constexpr std::strong_ordering operator<=>(const basic_iptr<TAG, TYPE>& a, const basic_iptr<TAG, TYPE>& b) noexcept; +}; + +// ----- constructors ----- + +template<typename Tag, typename T> constexpr fm_basic_iptr::basic_iptr() noexcept = default; +template<typename Tag, typename T> constexpr fm_basic_iptr::basic_iptr(std::nullptr_t) noexcept {} + +fm_template constexpr fm_basic_iptr& fm_basic_iptr::operator=(std::nullptr_t) noexcept +{ + if (_ptr) + { + ops_t::decr(_ptr); + _ptr = nullptr; + } + return *this; +} + +fm_template constexpr fm_basic_iptr::basic_iptr(fm_basic_iptr&& other) noexcept: _ptr{other._ptr} { other._ptr = nullptr; } + +fm_template constexpr fm_basic_iptr::basic_iptr(const fm_basic_iptr& other) noexcept: + _ptr{other._ptr} +{ + if (_ptr) + ops_t::incr(_ptr); +} + +fm_template constexpr fm_basic_iptr::basic_iptr(T* ptr) noexcept +{ + ops_t::init_to_1(ptr); +} + +// ----- destructor ----- + +fm_template constexpr fm_basic_iptr::~basic_iptr() noexcept { reset(); } + +// ----- assignment operators ----- + +fm_template constexpr fm_basic_iptr& fm_basic_iptr::operator=(fm_basic_iptr&& other) noexcept +{ + if (_ptr != other._ptr) + { + reset(); + _ptr = other._ptr; + other._ptr = nullptr; + } + return *this; +} + +fm_template constexpr fm_basic_iptr& fm_basic_iptr::operator=(const fm_basic_iptr& other) noexcept +{ +#ifdef __CLION_IDE__ + if (&other == this) + return *this; +#endif + if (other._ptr != _ptr) [[likely]] + { + if (_ptr) + ops_t::decr(_ptr); + _ptr = other._ptr; + if (_ptr) + ops_t::incr(_ptr); + } + return *this; +} + +fm_template template<typename... Ts> +requires std::is_constructible_v<T, Ts&&...> +constexpr fm_basic_iptr::basic_iptr(InPlaceInitT, Ts&&... args) // NOLINT(*-missing-std-forward) +noexcept(std::is_nothrow_constructible_v<T, Ts&&...>): + _ptr{new T{Utility::forward<Ts...>(args...)}} +{ + ops_t::init_to_1(_ptr); +} + +fm_template constexpr void fm_basic_iptr::reset() noexcept { if (_ptr) ops_t::decr(_ptr); } +fm_template constexpr void fm_basic_iptr::reset(T* ptr) noexcept { reset(); _ptr = ptr; } +fm_template constexpr void fm_basic_iptr::swap(basic_iptr& other) noexcept { auto p = _ptr; _ptr = other._ptr; other._ptr = p; } + +fm_template constexpr T* fm_basic_iptr::get() const noexcept +{ + fm_debug_assert((void*)_ptr != (void*)-1); // NOLINT(*-no-int-to-ptr) + return _ptr; +} + +fm_template constexpr T& fm_basic_iptr::operator*() const noexcept { return *get(); } +fm_template constexpr T* fm_basic_iptr::operator->() const noexcept { return get(); } + +fm_template constexpr auto fm_basic_iptr::use_count() const noexcept -> size_type +{ + if (!CORRADE_CONSTEVAL) + fm_debug_assert((void*)_ptr != (void*)-1); + return ops_t::count(_ptr); +} + +fm_template constexpr fm_basic_iptr::operator bool() const noexcept +{ + if (!CORRADE_CONSTEVAL) + fm_debug_assert((void*)_ptr != (void*)-1); + return _ptr != nullptr; +} + +fm_template constexpr bool operator==(const fm_basic_iptr& a, const fm_basic_iptr& b) noexcept +{ + if (!CORRADE_CONSTEVAL) + { + fm_debug_assert((void*)a._ptr != (void*)-1); + fm_debug_assert((void*)b._ptr != (void*)-1); + } + return a._ptr == b._ptr; +} + +fm_template constexpr std::strong_ordering operator<=>(const fm_basic_iptr& a, const fm_basic_iptr& b) noexcept +{ + return a._ptr <=> b._ptr; +} + +#undef fm_template +#undef fm_basic_iptr + +} // namespace floormat diff --git a/compat/intrusive-ptr.inl b/compat/intrusive-ptr.inl new file mode 100644 index 00000000..b8d40e3b --- /dev/null +++ b/compat/intrusive-ptr.inl @@ -0,0 +1,8 @@ +#pragma once +#include "intrusive-ptr.hpp" + +namespace floormat { + + + +} // namespace floormat diff --git a/test/app.hpp b/test/app.hpp index fc0bc90c..78794cd3 100644 --- a/test/app.hpp +++ b/test/app.hpp @@ -39,6 +39,7 @@ struct test_app final : private FM_APPLICATION static void test_wall_atlas(); static void test_wall_atlas2(); static void test_raycast(); + static void test_intrusive_ptr(); static void zzz_test_misc(); }; } // namespace floormat diff --git a/test/intrusive-ptr.cpp b/test/intrusive-ptr.cpp new file mode 100644 index 00000000..c37d5b2b --- /dev/null +++ b/test/intrusive-ptr.cpp @@ -0,0 +1,169 @@ +#include "app.hpp" +#include "compat/intrusive-ptr.hpp" +#include "compat/defs.hpp" + +#ifdef __CLION_IDE__ +#pragma clang diagnostic ignored "-Wunknown-pragmas" +#pragma ide diagnostic ignored "performance-unnecessary-copy-initialization" +#endif + +namespace floormat { + +namespace { struct Test2 { int val = 0; uint32_t _counter = 0; }; } + +template<> +constexpr auto +iptr::refcount_access<iptr::non_atomic_u32_tag, Test2>::access(Test2* ptr) noexcept -> counter_type& +{ + return ptr->_counter; +} + +namespace { + +struct non_copyable +{ + int value = 0; + explicit constexpr non_copyable(DirectInitT) {} + explicit constexpr non_copyable(DirectInitT, int value) : value{value} {} + fm_DECLARE_DEFAULT_MOVE_ASSIGNMENT(non_copyable); + fm_DECLARE_DELETED_COPY_ASSIGNMENT(non_copyable); +}; + +struct S { // todo fm_assert_equal + int instances; + unsigned allocs, frees; + bool operator==(const S&) const = default; +} s = {}; + +struct Test1 +{ + friend struct iptr::refcount_access<iptr::non_atomic_u32_tag, Test1>; + Test1(non_copyable nc) noexcept : value{nc.value} { s.instances++; s.allocs++; } + ~Test1() noexcept { s.instances--; fm_assert(s.instances >= 0); s.frees++; } + + int value = 0; + +private: + iptr::refcount_traits<iptr::non_atomic_u32_tag, Test1>::counter_type counter{0}; +}; + +} // namespace + +template<> +constexpr auto iptr::refcount_access<iptr::non_atomic_u32_tag, Test1>::access(Test1* ptr) noexcept -> counter_type& +{ + return ptr->counter; +} + +template class basic_iptr<iptr::non_atomic_u32_tag, Test1>; + +namespace { + +void test_copy() +{ + using myptr = local_iptr<Test1>; + + s = {}; + + { (void)myptr{nullptr}; + fm_assert(s == S{}); + (void)myptr{}; + fm_assert(s == S{}); + { Test1 t1{non_copyable{DirectInit}}; } + fm_assert(s != S{}); + fm_assert(s == S{0, 1, 1}); + + { Test1 t1{non_copyable{DirectInit}}; + fm_assert(s == S{1, 2, 1}); + } fm_assert(s == S{0, 2, 2}); + } + + { auto a = myptr{InPlaceInit, non_copyable{DirectInit}}; + fm_assert(s == S{1, 3, 2}); + auto b = a; + fm_assert(s == S{1, 3, 2}); + fm_assert(b.get() == a.get()); + } fm_assert(s == S{0, 3, 3}); + + { auto a = myptr{InPlaceInit, non_copyable{DirectInit}}; + fm_assert(s == S{1, 4, 3}); + a = {}; + fm_assert(s == S{0, 4, 4}); + } fm_assert(s == S{0, 4, 4}); + + { auto a = myptr{InPlaceInit, non_copyable{DirectInit, 1}}; + auto b = myptr{InPlaceInit, non_copyable{DirectInit, 2}}; + { auto c = myptr{InPlaceInit, non_copyable{DirectInit, 3}}; + auto d = myptr{InPlaceInit, non_copyable{DirectInit, 4}}; + } + fm_assert(a.use_count() == 1); + fm_assert(a->value == 1); + fm_assert(b->value == 2); + fm_assert(s == S{2, 8, 6}); + b = a; + fm_assert(b.use_count() == 2); + fm_assert(a.use_count() == 2); + b = nullptr; + fm_assert(b.use_count() == 0); + fm_assert(a.use_count() == 1); + fm_assert(s == S{1, 8, 7}); + fm_assert(a->value == 1); + } fm_assert(s == S{0, 8, 8}); +} + +#define fm_assert_free() do { fm_assert(s.allocs == s.frees); fm_assert(s.instances == 0); } while (false) + +void test_move() +{ + using myptr = local_iptr<Test1>; + using Utility::move; + + fm_assert_free(); + s = {}; + + { auto a = myptr{InPlaceInit, non_copyable{DirectInit, 1}}; + auto b = myptr{InPlaceInit, non_copyable{DirectInit, 2}}; + fm_assert(s == S{2, 2, 0}); + { auto c = myptr{InPlaceInit, non_copyable{DirectInit, 3}}; + auto d = myptr{InPlaceInit, non_copyable{DirectInit, 4}}; + fm_assert(s == S{4, 4, 0}); + } fm_assert(s == S{2, 4, 2}); + + { a = move(b); + fm_assert(s == S{1, 4, 3}); + fm_assert(!b); + fm_assert(a); + fm_assert(b.use_count() == 0); + fm_assert(a.use_count() == 1); + } + } fm_assert(s == S{0, 4, 4}); +} + +constexpr bool test_cexpr() // todo +{ + using myptr = local_iptr<Test2>; + using Utility::move; + + // construct + auto foo1 = myptr{}; + auto foo2 = myptr{nullptr}; + + fm_assert(foo1.use_count() == 0); + fm_assert(foo2.use_count() == 0); + foo1 = move(foo2); + fm_assert(foo1.use_count() == 0); + fm_assert(foo2.use_count() == 0); + + return true; +} + +} // namespace + +void test_app::test_iptr() +{ + static_assert(test_cexpr()); + test_copy(); + test_move(); +} + +} // namespace floormat diff --git a/test/main.cpp b/test/main.cpp index 3b77de46..7eb2b6af 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -28,6 +28,7 @@ int test_app::exec() test_entity(); test_math(); test_hash(); + test_intrusive_ptr(); test_loader(); test_bitmask(); test_wall_atlas(); |