Skip to content

Commit

Permalink
support array types
Browse files Browse the repository at this point in the history
  • Loading branch information
KRM7 committed Feb 18, 2024
1 parent 976a840 commit 18ff5cb
Show file tree
Hide file tree
Showing 3 changed files with 230 additions and 80 deletions.
35 changes: 0 additions & 35 deletions core-guidelines.ruleset

This file was deleted.

162 changes: 122 additions & 40 deletions src/small_unique_ptr.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,6 @@ namespace detail
inline constexpr bool is_proper_base_of_v = is_proper_base_of<Base, Derived>::value;


template<typename T, typename = void>
struct is_complete : std::false_type {};

template<typename T>
struct is_complete<T, std::void_t<decltype(sizeof(T))>> : std::true_type {};

template<typename T>
inline constexpr bool is_complete_v = is_complete<T>::value;


inline constexpr std::size_t small_ptr_size = 64;


Expand All @@ -78,6 +68,12 @@ namespace detail
static constexpr std::size_t value = std::has_virtual_destructor_v<T> ? dynamic_buffer_size : static_buffer_size;
};

template<typename T>
struct buffer_size<T[]>
{
static constexpr std::size_t value = small_ptr_size - sizeof(T*);
};

template<typename T>
inline constexpr std::size_t buffer_size_v = buffer_size<T>::value;

Expand All @@ -97,12 +93,32 @@ namespace detail


template<typename T>
struct is_always_heap_allocated
struct buffer_elements {};

template<typename T>
struct buffer_elements<T[]>
{
static constexpr std::size_t value = buffer_size_v<T[]> / sizeof(T);
};

template<typename T>
inline constexpr std::size_t buffer_elements_v = buffer_elements<T>::value;


template<typename T>
struct is_always_heap_allocated // TODO: cleanup?
{
static constexpr bool value = (sizeof(T) > buffer_size_v<T>) || (alignof(T) > buffer_alignment_v<T>) ||
(!std::is_abstract_v<T> && !std::is_nothrow_move_constructible_v<std::remove_cv_t<T>>);
};

template<typename T>
struct is_always_heap_allocated<T[]>
{
static constexpr bool value = (sizeof(T) > buffer_size_v<T[]>) || (alignof(T) > buffer_alignment_v<T[]>) ||
!std::is_nothrow_move_constructible_v<std::remove_cv_t<T>>;
};

template<typename T>
inline constexpr bool is_always_heap_allocated_v = is_always_heap_allocated<T>::value;

Expand All @@ -112,17 +128,17 @@ namespace detail
{
using pointer = std::remove_cv_t<T>*;
using buffer_t = unsigned char[buffer_size_v<T>];
using move_fn = void(*)(void* src, void* dst) noexcept;
using move_fn = void(*)(void*, void*) noexcept;

pointer buffer(std::ptrdiff_t offset = 0) const noexcept
{
return std::launder(reinterpret_cast<pointer>(static_cast<unsigned char*>(buffer_) + offset));
return reinterpret_cast<pointer>(static_cast<unsigned char*>(buffer_) + offset);
}

template<typename U>
void move_buffer_to(small_unique_ptr_base<U>& dst) noexcept
{
move_(buffer(), dst.buffer());
move_(std::launder(buffer()), dst.buffer());
dst.move_ = move_;
}

Expand All @@ -142,25 +158,25 @@ namespace detail
{
static constexpr bool is_stack_allocated() noexcept { return false; }

T* data_ = nullptr;
std::remove_extent_t<T>* data_ = nullptr;
};

template<typename T>
requires(!is_always_heap_allocated_v<T> && !std::is_polymorphic_v<T>)
requires(!is_always_heap_allocated_v<T> && !std::is_polymorphic_v<T> && !std::is_array_v<T>)
struct small_unique_ptr_base<T>
{
using pointer = std::remove_cv_t<T>*;
using buffer_t = unsigned char[buffer_size_v<T>];

pointer buffer(std::ptrdiff_t offset = 0) const noexcept
pointer buffer(std::ptrdiff_t = 0) const noexcept
{
return std::launder(reinterpret_cast<pointer>(static_cast<unsigned char*>(buffer_) + offset));
return reinterpret_cast<pointer>(static_cast<unsigned char*>(buffer_));
}

template<typename U>
void move_buffer_to(small_unique_ptr_base<U>& dst) noexcept
{
std::construct_at(dst.buffer(), std::move(*buffer()));
std::construct_at(dst.buffer(), std::move(*std::launder(buffer())));
}

constexpr bool is_stack_allocated() const noexcept
Expand All @@ -181,7 +197,7 @@ namespace detail

pointer buffer(std::ptrdiff_t offset = 0) const noexcept
{
return std::launder(reinterpret_cast<pointer>(static_cast<unsigned char*>(buffer_) + offset));
return reinterpret_cast<pointer>(static_cast<unsigned char*>(buffer_) + offset);
}

template<typename U>
Expand All @@ -196,9 +212,9 @@ namespace detail
{
if (std::is_constant_evaluated()) return false;

const volatile unsigned char* data = reinterpret_cast<const volatile unsigned char*>(data_);
const volatile unsigned char* buffer_first = static_cast<unsigned char*>(buffer_);
const volatile unsigned char* buffer_last = buffer_first + buffer_size_v<T>;
auto* data = reinterpret_cast<const volatile unsigned char*>(data_);
auto* buffer_first = static_cast<const volatile unsigned char*>(buffer_);
auto* buffer_last = buffer_first + buffer_size_v<T>;

assert(reinterpret_cast<std::uintptr_t>(buffer_last) - reinterpret_cast<std::uintptr_t>(buffer_first) == buffer_size_v<T>);

Expand All @@ -209,6 +225,33 @@ namespace detail
T* data_ = nullptr;
};

template<typename T>
requires(!is_always_heap_allocated_v<T> && std::is_array_v<T>)
struct small_unique_ptr_base<T>
{
using pointer = std::remove_cv_t<std::remove_extent_t<T>>*;
using buffer_t = unsigned char[buffer_size_v<T>];

pointer buffer(std::ptrdiff_t = 0) const noexcept
{
return reinterpret_cast<pointer>(static_cast<unsigned char*>(buffer_));
}

template<typename U>
void move_buffer_to(small_unique_ptr_base<U>& dst) noexcept
{
std::uninitialized_move(std::launder(buffer()), buffer() + buffer_elements_v<T>, dst.buffer());
}

constexpr bool is_stack_allocated() const noexcept
{
return !std::is_constant_evaluated() && (data_ == buffer());
}

alignas(buffer_alignment_v<T>) mutable buffer_t buffer_ = {};
std::remove_extent_t<T>* data_ = nullptr;
};

struct make_unique_small_impl;

} // namespace detail
Expand All @@ -218,11 +261,11 @@ template<typename T>
class small_unique_ptr : private detail::small_unique_ptr_base<T>
{
public:
static_assert(detail::is_complete_v<T> && !std::is_array_v<T>);
static_assert(!std::is_bounded_array_v<T>);

using element_type = T;
using pointer = T*;
using reference = T&;
using element_type = std::remove_extent_t<T>;
using pointer = std::remove_extent_t<T>*;
using reference = std::remove_extent_t<T>&;

struct constructor_tag_t {};

Expand All @@ -246,7 +289,7 @@ class small_unique_ptr : private detail::small_unique_ptr_base<T>
if constexpr (!detail::is_always_heap_allocated_v<U>) // other.is_stack_allocated()
{
other.move_buffer_to(*this);
this->data_ = this->buffer(other.template offsetof_base<T>());
this->data_ = std::launder(this->buffer(other.template offsetof_base<T>()));
other.reset();
}
}
Expand All @@ -272,7 +315,7 @@ class small_unique_ptr : private detail::small_unique_ptr_base<T>
{
reset();
other.move_buffer_to(*this);
this->data_ = this->buffer(other.template offsetof_base<T>());
this->data_ = std::launder(this->buffer(other.template offsetof_base<T>()));
other.reset();
}
return *this;
Expand All @@ -286,12 +329,12 @@ class small_unique_ptr : private detail::small_unique_ptr_base<T>

constexpr ~small_unique_ptr() noexcept
{
is_stack_allocated() ? std::destroy_at(this->data_) : delete this->data_;
destroy();
}

constexpr void reset(pointer new_data = pointer{}) noexcept
{
is_stack_allocated() ? std::destroy_at(this->data_) : delete this->data_;
destroy();
if constexpr (requires { small_unique_ptr::move_; }) this->move_ = nullptr;
this->data_ = new_data;
}
Expand All @@ -314,26 +357,26 @@ class small_unique_ptr : private detail::small_unique_ptr_base<T>
detail::small_unique_ptr_base<T> temp;

other.move_buffer_to(temp);
temp.data_ = temp.buffer(other_offset);
temp.data_ = std::launder(temp.buffer(other_offset));
std::destroy_at(other.data_);

this->move_buffer_to(other);
other.data_ = other.buffer(this_offset);
other.data_ = std::launder(other.buffer(this_offset));
std::destroy_at(this->data_);

temp.move_buffer_to(*this);
this->data_ = this->buffer(other_offset);
this->data_ = std::launder(this->buffer(other_offset));
std::destroy_at(temp.data_);
}
else if (!is_stack_allocated() && other.is_stack_allocated())
{
const pointer new_data = this->buffer(other.offsetof_base());
const pointer new_data = std::launder(this->buffer(other.offsetof_base()));
other.move_buffer_to(*this);
other.reset(std::exchange(this->data_, new_data));
}
else /* if (is_stack_allocated() && !other.is_stack_allocated()) */
{
const pointer new_data = other.buffer(this->offsetof_base());
const pointer new_data = std::launder(other.buffer(this->offsetof_base()));
this->move_buffer_to(other);
this->reset(std::exchange(other.data_, new_data));
}
Expand Down Expand Up @@ -374,19 +417,26 @@ class small_unique_ptr : private detail::small_unique_ptr_base<T>
}

[[nodiscard]]
constexpr reference operator*() const noexcept(detail::is_nothrow_dereferenceable_v<pointer>)
constexpr reference operator*() const noexcept(detail::is_nothrow_dereferenceable_v<pointer>) requires(!std::is_array_v<T>)
{
assert(this->data_);
return *this->data_;
}

[[nodiscard]]
constexpr pointer operator->() const noexcept
constexpr pointer operator->() const noexcept requires(!std::is_array_v<T>)
{
assert(this->data_);
return this->data_;
}

[[nodiscard]]
constexpr reference operator[](std::size_t idx) const requires(std::is_array_v<T>)
{
assert(this->data_);
return this->data_[idx];
}

constexpr bool operator==(std::nullptr_t) const noexcept
{
return this->data_ == pointer{ nullptr };
Expand Down Expand Up @@ -427,11 +477,23 @@ class small_unique_ptr : private detail::small_unique_ptr_base<T>
if (!is_stack_allocated()) return 0;

const auto derived_ptr = reinterpret_cast<const volatile unsigned char*>(this->buffer());
const auto base_ptr = reinterpret_cast<const volatile unsigned char*>(static_cast<const volatile Base*>(this->data_));
const auto base_ptr = reinterpret_cast<const volatile unsigned char*>(static_cast<const volatile std::remove_extent_t<Base>*>(this->data_)); // TODO: ugly code

return base_ptr - derived_ptr;
}

constexpr void destroy() noexcept // TODO: cleanup?
{
if constexpr (!std::is_array_v<T>)
{
is_stack_allocated() ? std::destroy_at(this->data_) : delete this->data_;
}
else
{
is_stack_allocated() ? std::destroy(this->data_, this->data_ + detail::buffer_elements_v<T>) : delete[] this->data_;
}
}

template<typename U>
friend class small_unique_ptr;

Expand All @@ -456,6 +518,7 @@ namespace detail
struct make_unique_small_impl
{
template<typename T, typename... Args>
requires(!std::is_array_v<T>)
static constexpr small_unique_ptr<T> invoke(Args&&... args)
noexcept(std::is_nothrow_constructible_v<T, Args...> && !detail::is_always_heap_allocated_v<T>)
{
Expand All @@ -477,12 +540,31 @@ namespace detail

return ptr;
}

template<typename T>
requires(std::is_unbounded_array_v<T>)
static constexpr small_unique_ptr<T> invoke(std::size_t count) // TODO: think about what happens if count == 0
{
small_unique_ptr<T> ptr;

if (detail::is_always_heap_allocated_v<T> || (count > detail::buffer_elements_v<T>) || std::is_constant_evaluated())
{
ptr.data_ = new std::remove_extent_t<T>[count](); // TODO: with () this isnt constexpr under msvc?
}
else if constexpr (!detail::is_always_heap_allocated_v<T>)
{
std::uninitialized_value_construct(ptr.buffer(), ptr.buffer() + detail::buffer_elements_v<T>);
ptr.data_ = ptr.buffer();
}

return ptr;
}
};

} // namespace detail

template<typename T, typename... Args>
[[nodiscard]] constexpr small_unique_ptr<T> make_unique_small(Args&&... args)
[[nodiscard]] constexpr small_unique_ptr<T> make_unique_small(Args&&... args) // TODO: add array overload?
noexcept(std::is_nothrow_constructible_v<T, Args...> && !detail::is_always_heap_allocated_v<T>)
{
return detail::make_unique_small_impl::invoke<T>(std::forward<Args>(args)...);
Expand Down
Loading

0 comments on commit 18ff5cb

Please sign in to comment.