Skip to content

Commit

Permalink
experimental
Browse files Browse the repository at this point in the history
  • Loading branch information
KRM7 committed Jan 3, 2024
1 parent 539f433 commit 9f360d3
Show file tree
Hide file tree
Showing 3 changed files with 615 additions and 0 deletions.
319 changes: 319 additions & 0 deletions src/utility/small_unique_ptr.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,319 @@
/* Copyright (c) 2023 Krisztián Rugási. Subject to the MIT License. */

#ifndef GA_UTILITY_SMALL_UNIQUE_PTR_HPP
#define GA_UTILITY_SMALL_UNIQUE_PTR_HPP

#include "type_traits.hpp"
#include "utility.hpp"
#include <compare>
#include <memory>
#include <type_traits>
#include <utility>
#include <cstddef>

namespace gapp::detail
{
template<typename T>
inline constexpr size_t buffer_size_v = 64 - sizeof(T*) - sizeof(void(*)());

// should be inside small_unique_ptr
template<typename T>
inline constexpr bool always_heap_allocated_v = (sizeof(T) > buffer_size_v<T>) || (!std::is_nothrow_move_constructible_v<T> && !std::is_abstract_v<T>);


template<typename T, bool = always_heap_allocated_v<T>>
class small_ptr_storage
{
public:
constexpr small_ptr_storage() noexcept {}
constexpr ~small_ptr_storage() noexcept {}

small_ptr_storage(const small_ptr_storage&) = delete;
small_ptr_storage(small_ptr_storage&&) = delete;

small_ptr_storage& operator=(const small_ptr_storage&) = delete;
small_ptr_storage& operator=(small_ptr_storage&&) = delete;

constexpr T* buffer() noexcept
{
if constexpr (!std::is_abstract_v<T>) return std::addressof(buffer_);
else return reinterpret_cast<T*>(std::addressof(buffer_));
}

constexpr const T* buffer() const noexcept
{
if constexpr (!std::is_abstract_v<T>) return std::addressof(buffer_);
else return reinterpret_cast<const T*>(std::addressof(buffer_));
}

template<typename U>
constexpr void move_buffer_to(small_ptr_storage<U>& dst) noexcept
{
move_(buffer(), dst.buffer());
dst.move_ = move_;
}

using buffer_t = std::conditional_t<std::is_abstract_v<T>, unsigned char[sizeof(T)], T>;
using move_t = void(*)(void* src, void* dst) noexcept;

move_t move_ = nullptr;
union
{
unsigned char padding_[buffer_size_v<T>];
alignas(T) buffer_t buffer_;
};
};

template<typename T>
class small_ptr_storage<T, true>
{};


template<typename T> // TODO: move CACHE_LINE_SIZE constant from small_vector to utility and use that here too
class small_unique_ptr : private small_ptr_storage<std::remove_cv_t<T>>
{
public:
static_assert(!std::is_array_v<T> && !std::is_void_v<T>);

using element_type = T;
using pointer = T*;
using reference = T&;

constexpr small_unique_ptr() noexcept = default;
constexpr small_unique_ptr(std::nullptr_t) noexcept {}

constexpr small_unique_ptr(small_unique_ptr&& other) noexcept :
small_unique_ptr<T>(std::move(other), detail::empty_t{})
{}

template<typename U>
constexpr small_unique_ptr(small_unique_ptr<U>&& other, detail::empty_t = {}) noexcept
{
static_assert(!detail::is_proper_base_of_v<T, U> || std::has_virtual_destructor_v<T>);

if (!other.is_local() || std::is_constant_evaluated())
{
data_ = std::exchange(other.data_, nullptr);
return;
}
//if constexpr (!always_heap_allocated_v<U> && !std::has_virtual_destructor_v<T>)
//{
// std::construct_at(this->buffer(), std::move(*other.buffer()));
// data_ = this->buffer();
// other.reset();
//}
if constexpr (!always_heap_allocated_v<U>) // other.is_local()
{
other.move_buffer_to(*this);
data_ = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(this->buffer()) + offsetof_base(other));
other.reset();
}
}

constexpr small_unique_ptr& operator=(small_unique_ptr&& other) noexcept
{
if (std::addressof(other) == this) [[unlikely]] return *this;

return operator=<T>(std::move(other));
}

template<typename U>
constexpr small_unique_ptr& operator=(small_unique_ptr<U>&& other) noexcept
{
static_assert(!detail::is_proper_base_of_v<T, U> || std::has_virtual_destructor_v<T>);

if (!other.is_local() || std::is_constant_evaluated())
{
reset(std::exchange(other.data_, nullptr));
return *this;
}
if constexpr (!always_heap_allocated_v<U>) // other.is_local()
{
const ptrdiff_t old_offset = offsetof_this();
reset();
other.move_buffer_to(*this);
data_ = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(this->buffer()) + offsetof_base(other) + old_offset);
other.reset();
}
return *this;
}

constexpr small_unique_ptr& operator=(std::nullptr_t) noexcept
{
reset();
return *this;
}

constexpr ~small_unique_ptr() noexcept
{
reset();
}

constexpr void reset(pointer new_data = pointer{}) noexcept
{
is_local() ? std::destroy_at(data_) : delete data_;
data_ = new_data;
if constexpr (!always_heap_allocated_v<T>) this->move_ = nullptr; // maybe add reset() method to buffer?
}

constexpr void swap(small_unique_ptr& other) noexcept requires(always_heap_allocated_v<T>)
{
std::swap(data_, other.data_);
}

constexpr void swap(small_unique_ptr& other) noexcept requires(!always_heap_allocated_v<T>)
{
if (is_local() && other.is_local())
{
T* new_this_data = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(this->buffer()) + other.offsetof_this());
T* new_other_data = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(other.buffer()) + offsetof_this());

small_ptr_storage<T> temp;

other.move_buffer_to(temp);
std::destroy_at(other.buffer());
this->move_buffer_to(other);
std::destroy_at(this->buffer());
temp.move_buffer_to(*this);
std::destroy_at(temp.buffer());

this->data_ = new_this_data;
other.data_ = new_other_data;
}
else if (!is_local() && !other.is_local())
{
std::swap(data_, other.data_);
}
else if (!is_local() && other.is_local())
{
T* new_data = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(this->buffer()) + other.offsetof_this());
other.move_buffer_to(*this);
other.reset(std::exchange(data_, new_data));
}
else /* if (is_local() && !other.is_local()) */
{
T* new_data = reinterpret_cast<T*>(reinterpret_cast<unsigned char*>(other.buffer()) + offsetof_this());
this->move_buffer_to(other);
reset(std::exchange(other.data_, new_data));
}
}

[[nodiscard]]
constexpr pointer get() const noexcept
{
return data_;
}

[[nodiscard]]
constexpr explicit operator bool() const noexcept
{
return bool(data_);
}

[[nodiscard]]
constexpr reference operator*() const noexcept(detail::is_nothrow_dereferenceable_v<pointer>)
{
GAPP_ASSERT(data_);
return *data_;
}

[[nodiscard]]
constexpr pointer operator->() const noexcept
{
GAPP_ASSERT(data_);
return data_;
}

constexpr bool operator==(std::nullptr_t) const noexcept
{
return data_ == pointer{ nullptr };
}

constexpr std::strong_ordering operator<=>(std::nullptr_t) const noexcept
{
return data_ <=> pointer{ nullptr };
}

template<typename U>
constexpr bool operator==(const small_unique_ptr<U>& rhs) const noexcept
{
return data_ == rhs.data_;
}

template<typename U>
constexpr std::strong_ordering operator<=>(const small_unique_ptr<U>& rhs) const noexcept
{
return data_ <=> rhs.data_;
}

private:
pointer data_ = nullptr;

constexpr bool is_local() const noexcept requires(!always_heap_allocated_v<T>) { return this->move_ != nullptr; }
constexpr bool is_local() const noexcept requires(always_heap_allocated_v<T>) { return false; }

template<typename U>
constexpr ptrdiff_t offsetof_base(const small_unique_ptr<U>& other) const noexcept
{
if constexpr (!detail::is_proper_base_of_v<T, U>) return 0;
else
{
const auto derived_ptr = reinterpret_cast<const volatile unsigned char*>(other.get());
const auto base_ptr = reinterpret_cast<const volatile unsigned char*>(static_cast<const volatile T*>(other.get()));

return base_ptr - derived_ptr;
}
}

constexpr ptrdiff_t offsetof_this() const noexcept
{
if (!is_local() || !data_) return 0;

const auto derived_ptr = reinterpret_cast<const volatile unsigned char*>(this->buffer());
const auto base_ptr = reinterpret_cast<const volatile unsigned char*>(data_);

return base_ptr - derived_ptr;
}

template<typename U>
friend class small_unique_ptr;

template<typename U, typename... Args>
friend constexpr small_unique_ptr<U> make_unique_small(Args&&...);
};

template<typename T>
constexpr void swap(small_unique_ptr<T>& lhs, small_unique_ptr<T>& rhs) noexcept
{
lhs.swap(rhs);
}


template<typename T>
void move_buffer(void* src, void* dst) noexcept
{
std::construct_at(static_cast<T*>(dst), std::move(*static_cast<T*>(src)));
}

template<typename T, typename... Args>
[[nodiscard]] constexpr small_unique_ptr<T> make_unique_small(Args&&... args)
{
small_unique_ptr<T> ptr;

if constexpr (always_heap_allocated_v<T> || std::is_constant_evaluated())
{
ptr.data_ = new T(std::forward<Args>(args)...);
}
else
{
std::construct_at(ptr.buffer(), std::forward<Args>(args)...);
ptr.data_ = ptr.buffer();
ptr.move_ = move_buffer<T>;
}

return ptr;
}

} // namespace gapp::detail

#endif // !GA_UTILITY_SMALL_UNIQUE_PTR_HPP
18 changes: 18 additions & 0 deletions src/utility/type_traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,17 @@ namespace gapp::detail



template<typename Base, typename Derived>
struct is_proper_base_of :
std::conjunction<std::is_base_of<std::remove_cv<Base>, std::remove_cv<Derived>>,
std::negation<std::is_same<std::remove_cv<Base>, std::remove_cv<Derived>>>>
{};

template<typename Base, typename Derived>
inline constexpr bool is_proper_base_of_v = is_proper_base_of<Base, Derived>::value;



template<typename Derived, template<typename...> class BaseTempl>
struct is_derived_from_spec_of
{
Expand Down Expand Up @@ -161,6 +172,13 @@ namespace gapp::detail
using promoted_t = typename promoted<T>::type;



template<typename T>
struct is_nothrow_dereferenceable : std::bool_constant<noexcept(*std::declval<T>())> {};

template<typename T>
inline constexpr bool is_nothrow_dereferenceable_v = is_nothrow_dereferenceable<T>::value;

} // namespace gapp::detail

#endif // !GA_UTILITY_TYPE_TRAITS_HPP
Loading

0 comments on commit 9f360d3

Please sign in to comment.