diff --git a/include/ktl/containers/inline_vector.h b/include/ktl/containers/inline_vector.h new file mode 100644 index 0000000..7cd65ec --- /dev/null +++ b/include/ktl/containers/inline_vector.h @@ -0,0 +1,563 @@ +#pragma once + +#include "../utility/assert.h" +#include "../utility/empty_base.h" +#include "inline_vector_fwd.h" + +#include +#include +#include +#include + +namespace ktl +{ + /** + * @brief A stack allocated vector with a given maximum capacity + * @tparam T The type to use + * @tparam Capacity The capacity for the vector + */ + template + class inline_vector + { + private: + static_assert(std::is_default_constructible::value, "Template class needs to be default constructible"); + + public: + using value_type = T; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + + using reference = T&; + using const_reference = const T&; + + using iterator = T*; + using const_iterator = const T*; + + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + public: + /** + * @brief Construct the vector with a default constructed allocator + */ + inline_vector() noexcept : + m_Buffer{}, + m_Size(0) {} + + /** + * @brief Construct the vector with the given allocator and initial size + * @param n The initial size of the vector + * @param allocator The allocator to use. Will be default constructed if unspecified + */ + explicit inline_vector(size_t n) : + m_Buffer{}, + m_Size(n) + { + KTL_ASSERT(n <= Capacity); + + if constexpr (!std::is_trivial_v) + { + for (auto iter = begin(); iter != end(); ++iter) + { + new (iter) T(); + } + } + } + + /** + * @brief Construct the vector with the given allocator, initial size and default value + * @param n The initial size of the vector + * @param value The value to initialize every element as + * @param allocator The allocator to use. Will be default constructed if unspecified + */ + explicit inline_vector(size_t n, const T& value) : + m_Buffer{}, + m_Size(n) + { + KTL_ASSERT(n <= Capacity); + + std::uninitialized_fill_n(reinterpret_cast(m_Buffer), n, value); + } + + /** + * @brief Construct the vector with the allocator and range of values + * @param initializer The initial set of values + * @param allocator The allocator to use. Will be default constructed if unspecified + */ + inline_vector(std::initializer_list initializer) : + m_Buffer{}, + m_Size(0) + { + KTL_ASSERT(initializer.size() <= Capacity); + + for (auto& value : initializer) + { + emplace_back(value); + } + } + + /** + * @brief Construct the vector with the allocator and range of values + * @param first A pointer to the first element + * @param last A pointer past the last element + * @param allocator The allocator to use. Will be default constructed if unspecified + */ + explicit inline_vector(const T* first, const T* last) : + m_Buffer{}, + m_Size(last - first) + { + if constexpr (std::is_trivial_v) + { + size_t n = last - first; + + std::memcpy(m_Buffer, first, n * sizeof(T)); + } + else + { + m_Size = 0; + + for (; first != last; ++first) + { + emplace_back(*first); + } + } + } + + inline_vector(const inline_vector& other) noexcept : + m_Buffer{}, + m_Size(other.m_Size) + { + if constexpr (std::is_trivial_v) + { + std::memcpy(m_Buffer, other.m_Buffer, other.size() * sizeof(T)); + } + else + { + m_Size = 0; + + for (auto& v : other) + { + emplace_back(v); + } + } + } + + inline_vector(inline_vector&& other) noexcept : + m_Buffer{}, + m_Size(other.m_Size) + { + if constexpr (std::is_trivial_v) + { + std::memcpy(m_Buffer, other.m_Buffer, other.size() * sizeof(T)); + } + else + { + m_Size = 0; + + for (auto& v : other) + { + emplace_back(std::move(v)); + v.~T(); + } + } + + other.m_Size = 0; + } + + ~inline_vector() noexcept + { + if constexpr (!std::is_trivial_v) + { + for (auto iter = begin(); iter != end(); ++iter) + { + iter->~T(); + } + } + } + + inline_vector& operator=(const inline_vector& other) noexcept + { + if constexpr (std::is_trivial_v) + { + m_Size = other.m_Size; + std::memcpy(m_Buffer, other.m_Buffer, other.size() * sizeof(T)); + } + else + { + m_Size = 0; + + for (auto& v : other) + { + emplace_back(v); + } + } + + return *this; + } + + inline_vector& operator=(inline_vector&& other) noexcept + { + if constexpr (std::is_trivial_v) + { + m_Size = other.m_Size; + std::memcpy(m_Buffer, other.m_Buffer, other.size() * sizeof(T)); + } + else + { + m_Size = 0; + + for (auto& v : other) + { + emplace_back(std::move(v)); + v.~T(); + } + } + + other.m_Size = 0; + + return *this; + } + + friend bool operator==(const inline_vector& lhs, const inline_vector& rhs) noexcept + { + if (lhs.size() != rhs.size()) + return false; + + if constexpr (std::is_trivial_v) + { + return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0; + } + else + { + for (size_t i = 0; i < lhs.size(); ++i) + { + if (lhs.at(i) != rhs.at(i)) + return false; + } + + return true; + } + } + + friend bool operator!=(const inline_vector& lhs, const inline_vector& rhs) noexcept + { + return !(lhs == rhs); + } + + /** + * @brief Returns a reference to the element at @p index. + * @note An index higher than size() will produce undefined behaviour. + * @param index The index of the element in the array. Must be less than size(). + * @return A reference to the element at @p index. + */ + T& operator[](size_t index) noexcept { KTL_ASSERT(index < size()); return *iterator_at_index(index); } + + /** + * @brief Returns a reference to the element at @p index. + * @note An index higher than size() will produce undefined behaviour. + * @param index The index of the element in the array. Must be less than size(). + * @return A reference to the element at @p index. + */ + const T& operator[](size_t index) const noexcept { KTL_ASSERT(index < size()); return *iterator_at_index(index); } + + + iterator begin() noexcept { return iterator_at_index(0); } + + const_iterator begin() const noexcept { return iterator_at_index(0); } + + iterator end() noexcept { return iterator_at_index(m_Size); } + + const_iterator end() const noexcept { return iterator_at_index(m_Size); } + + reverse_iterator rbegin() noexcept { return std::reverse_iterator(end()); } + + const_reverse_iterator rbegin() const noexcept { return std::reverse_iterator(end()); } + + reverse_iterator rend() noexcept { return std::reverse_iterator(begin()); } + + const_reverse_iterator rend() const noexcept { return std::reverse_iterator(begin()); } + + reference front() noexcept { return *begin(); } + + const_reference front() const noexcept { return *begin(); } + + reference back() noexcept { return *rbegin(); } + + const_reference back() const noexcept { return *rbegin(); } + + + /** + * @brief Returns the current size of the vector. + * @return The current size of the vector in number of elements. + */ + size_t size() const noexcept { return m_Size; } + + /** + * @brief Returns the capacity of the vector. + * @return The capacity of the vector in number of elements. + */ + constexpr size_t max_size() const noexcept { return Capacity; } + + /** + * @brief Returns the capacity of the vector. + * @return The capacity of the vector in number of elements. + */ + constexpr size_t capacity() const noexcept { return Capacity; } + + /** + * @brief Returns true if the vector has no elements. + * @return Whether the vector has a size of 0. + */ + bool empty() const noexcept { return m_Size == 0; } + + + /** + * @brief Returns an iterator to the start of the vector. + * @return An iterator to the start of the vector. + */ + iterator data() noexcept { return begin(); } + + /** + * @brief Returns a const iterator to the start of the vector. + * @return A const iterator to the start of the vector. + */ + const_iterator data() const noexcept { return begin(); } + + /** + * @brief Returns a reference to the element at @p index. + * @note An index higher than size() will produce undefined behaviour. + * @param index The index of the element in the vector. Must be less than size(). + * @return A reference to the element at @p index. + */ + reference at(size_t index) noexcept { KTL_ASSERT(index < size()); return *iterator_at_index(index); } + + /** + * @brief Returns a reference to the element at @p index. + * @note An index higher than size() will produce undefined behaviour. + * @param index The index of the element in the vector. Must be less than size(). + * @return A reference to the element at @p index. + */ + const_reference at(size_t index) const noexcept { KTL_ASSERT(index < size()); return *iterator_at_index(index); } + + + /** + * @brief Resizes the vector to the given size. + * @param n The size to resize to. + */ + void resize(size_t n) noexcept + { + KTL_ASSERT(n <= Capacity); + + if constexpr (!std::is_trivial_v) + { + for (size_t i = m_Size; i < n; ++i) + { + new (iterator_at_index(i)) T(); + } + + for (size_t i = n; i < m_Size; ++i) + { + iterator_at_index(i)->~T(); + } + } + + m_Size = n; + } + + /** + * @brief Pushes a new element into the vector by copying it. + * @param value The element to copy into the vector. + */ + void push_back(const T& element) noexcept + { + emplace_back(element); + } + + /** + * @brief Pushes a new element into the vector by moving it. + * @param value The element to move into the vector. + */ + void push_back(T&& element) noexcept + { + emplace_back(std::move(element)); + } + + /** + * @brief Pushes a new element into the vector by constructing it. + * @tparam ...Args Variadic template arguments. + * @param ...args Any arguments to use in the construction of the element. + * @return An iterator to the element that was added. + */ + template + reference emplace_back(Args&&... args) noexcept + { + KTL_ASSERT(m_Size < Capacity); + + T* index = iterator_at_index(m_Size); + + new (index) T(std::forward(args)...); + + ++m_Size; + + return *index; + } + + /** + * @brief Inserts a new element into the vector by constructing it. + * @tparam ...Args Variadic template arguments. + * @param iter An iterator pointing to the location where the new element should be emplaced. + * @param ...args Any arguments to use in the construction of the element. + * @return An iterator to the element that was added. + */ + template + iterator emplace(const_iterator const_iter, Args&&... args) noexcept + { + KTL_ASSERT(const_iter >= begin() && const_iter <= end()); + KTL_ASSERT(m_Size + 1 <= Capacity); + + T* iter = const_cast(const_iter); + + if constexpr (std::is_trivial_v) + { + std::memmove(iter + 1, iter, (end() - iter) * sizeof(T)); + } + else + { + T* last = end(); + T* next_last = end() - 1; + + if (iter != last) + { + for (auto own_iter = iter; own_iter != next_last; ++own_iter) + { + *own_iter = std::move(*(own_iter + 1)); + } + + new (last) T(std::move(*next_last)); + } + } + + *iter = T(std::forward(args)...); + + ++m_Size; + + return iter; + } + + /** + * @brief Erases the element pointed to by the iterator. + * @param iter An iterator pointing to the element. + * @return An iterator pointing to the element immidiately after the erased one. + */ + iterator erase(const_iterator iter) noexcept + { + KTL_ASSERT(iter >= begin() && iter <= end()); + + if constexpr (std::is_trivial_v) + { + std::memmove(const_cast(iter), iter + 1, ((end() - iter) - 1) * sizeof(T)); + } + else + { + for (auto own_iter = const_cast(iter); own_iter != end() - 1; ++own_iter) + { + *own_iter = std::move(*(own_iter + 1)); + } + + rbegin()->~T(); + } + + --m_Size; + + return const_cast(iter); + } + + /** + * @brief Erases all elements in a range. + * @param first An iterator pointing to the first element. + * @param last An iterator pointing to the location after the last element. + * @return An iterator pointing to the element immidiately after the erased ones. + */ + iterator erase(const_iterator first, const_iterator last) noexcept + { + KTL_ASSERT(first <= last); + KTL_ASSERT(first >= begin() && last <= end()); + + size_t diff = last - first; + + if constexpr (std::is_trivial_v) + { + std::memmove(const_cast(first), last, (end() - last) * sizeof(T)); + } + else + { + for (auto iter = const_cast(first); iter != end() - diff; ++iter) + { + *iter = std::move(*(iter + diff)); + } + + for (auto iter = end() - diff; iter != end(); ++iter) + { + iter->~T(); + } + } + + m_Size -= diff; + + return const_cast(first); + } + + /** + * @brief Removes the last element from the vector and returns it. + * @return The last element in the vector. + */ + value_type pop_back() noexcept + { + if constexpr (std::is_trivial_v) + { + return *iterator_at_index(--m_Size); + } + else + { + T* iter = iterator_at_index(--m_Size); + + T value = std::move(*iter); + + iter->~T(); + + return value; + } + } + + /** + * @brief Clears all elements in the vector. + */ + void clear() noexcept + { + if constexpr (!std::is_trivial_v) + { + for (auto iter = begin(); iter != end(); ++iter) + { + iter->~T(); + } + } + + m_Size = 0; + } + + private: + T* iterator_at_index(size_t index) noexcept + { + return std::launder(reinterpret_cast(m_Buffer) + index); + } + + const T* iterator_at_index(size_t index) const noexcept + { + return std::launder(reinterpret_cast(m_Buffer) + index); + } + + private: + alignas(T) uint8_t m_Buffer[Capacity * sizeof(T)]; + size_t m_Size; + }; +} \ No newline at end of file diff --git a/include/ktl/containers/inline_vector_fwd.h b/include/ktl/containers/inline_vector_fwd.h new file mode 100644 index 0000000..bd13240 --- /dev/null +++ b/include/ktl/containers/inline_vector_fwd.h @@ -0,0 +1,7 @@ +#pragma once + +namespace ktl +{ + template + class inline_vector; +} \ No newline at end of file diff --git a/include/ktl/containers/sbo_vector.h b/include/ktl/containers/sbo_vector.h new file mode 100644 index 0000000..703c73c --- /dev/null +++ b/include/ktl/containers/sbo_vector.h @@ -0,0 +1,569 @@ +#pragma once + +#include "../utility/assert.h" +#include "../utility/unreachable.h" +#include "sbo_vector_fwd.h" + +#include "inline_vector.h" + +#include +#include +#include +#include +#include +#include + +namespace ktl +{ + template + class sbo_vector + { + public: + using value_type = typename Vec::value_type; + + private: + static_assert(std::is_default_constructible::value, "Template class needs to be default constructible"); + + static constexpr size_t InlineCapacityInBytes = sizeof(Vec) - sizeof(size_t); + static constexpr size_t InlineCapacity = (InlineCapacityInBytes + sizeof(value_type) - 1) / sizeof(value_type); + + using inline_vec = inline_vector; + using variant_type = std::variant; + + public: + using size_type = typename Vec::size_type; + using difference_type = std::ptrdiff_t; + using allocator_type = typename Vec::allocator_type; + + using reference = value_type&; + using const_reference = const value_type&; + + using iterator = value_type*; + using const_iterator = const value_type*; + + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + static_assert(std::is_same_v, "Vector class must use T& as reference type"); + + public: + /** + * @brief Construct the vector with a default constructed allocator + */ + sbo_vector() noexcept : + m_Data(std::in_place_type, 0) {} + + /** + * @brief Construct the vector with the given allocator + * @param allocator The allocator to use + */ + explicit sbo_vector(const allocator_type& alloc) noexcept : + m_Data(std::in_place_type, alloc) {} + + /** + * @brief Construct the vector with the vector + * @param vec The vector to use + */ + explicit sbo_vector(const Vec& vec) noexcept : + m_Data(std::in_place_type, vec) {} + + /** + * @brief Construct the vector with the given vector + * @param vec The vector to use + */ + explicit sbo_vector(Vec&& vec) noexcept : + m_Data(std::in_place_type, std::move(vec)) {} + + /** + * @brief Construct the vector with the given allocator and initial size + * @param n The initial size of the vector + * @param allocator The allocator to use. Will be default constructed if unspecified + */ + explicit sbo_vector(size_t n, const allocator_type& allocator = allocator_type()) : + m_Data(n > InlineCapacity + ? variant_type{ std::in_place_type, n, allocator } + : variant_type{ std::in_place_type, n }) + {} + + /** + * @brief Construct the vector with the given allocator, initial size and default value + * @param n The initial size of the vector + * @param value The value to initialize every element as + * @param allocator The allocator to use. Will be default constructed if unspecified + */ + explicit sbo_vector(size_t n, const value_type& value, const allocator_type& allocator = allocator_type()) : + m_Data(n > InlineCapacity + ? variant_type{ std::in_place_type, n, value, allocator } + : variant_type{ std::in_place_type, n, value }) + {} + + /** + * @brief Construct the vector with the allocator and range of values + * @param initializer The initial set of values + * @param allocator The allocator to use. Will be default constructed if unspecified + */ + sbo_vector(std::initializer_list initializer, const allocator_type& allocator = allocator_type()) : + m_Data(initializer.size() > InlineCapacity + ? variant_type{ std::in_place_type, initializer, allocator } + : variant_type{ std::in_place_type, initializer }) + {} + + /** + * @brief Construct the vector with the allocator and range of values + * @param first A pointer to the first element + * @param last A pointer past the last element + * @param allocator The allocator to use. Will be default constructed if unspecified + */ + explicit sbo_vector(const_iterator first, const_iterator last, const allocator_type& allocator = allocator_type()) : + m_Data((last - first) > InlineCapacity + ? variant_type{ std::in_place_type, first, last, allocator } + : variant_type{ std::in_place_type, first, last }) + {} + + friend bool operator==(const sbo_vector& lhs, const sbo_vector& rhs) noexcept + { + if (lhs.m_Data.index() != rhs.m_Data.index()) + { + if (lhs.size() != rhs.size()) + return false; + + if constexpr (std::is_trivial_v) + { + return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0; + } + else + { + for (size_t i = 0; i < lhs.size(); ++i) + { + if (lhs.at(i) != rhs.at(i)) + return false; + } + + return true; + } + } + + switch (lhs.m_Data.index()) + { + case 0: + return std::get(lhs.m_Data) == std::get(rhs.m_Data); + case 1: + return std::get(lhs.m_Data) == std::get(rhs.m_Data); + default: + KTL_UNREACHABLE(); + } + } + + friend bool operator!=(const sbo_vector& lhs, const sbo_vector& rhs) noexcept + { + return !(lhs == rhs); + } + + /** + * @brief Returns a reference to the element at @p index. + * @note An index higher than size() will produce undefined behaviour. + * @param index The index of the element in the array. Must be less than size(). + * @return A reference to the element at @p index. + */ + reference operator[](size_t index) noexcept + { + return std::visit([&index](auto& v) -> reference { return v[index]; }, m_Data); + } + + /** + * @brief Returns a reference to the element at @p index. + * @note An index higher than size() will produce undefined behaviour. + * @param index The index of the element in the array. Must be less than size(). + * @return A reference to the element at @p index. + */ + const_reference operator[](size_t index) const noexcept + { + return std::visit([&index](auto& v) -> const_reference { return v[index]; }, m_Data); + } + + + iterator begin() noexcept + { + return std::visit([this](auto& v) { return to_inline_iterator(v.begin()); }, m_Data); + } + + const_iterator begin() const noexcept + { + return std::visit([this](auto& v) { return to_inline_iterator(v.begin()); }, m_Data); + } + + iterator end() noexcept + { + return std::visit([this](auto& v) { return to_inline_iterator(v.end()); }, m_Data); + } + + const_iterator end() const noexcept + { + return std::visit([this](auto& v) { return to_inline_iterator(v.end()); }, m_Data); + } + + reverse_iterator rbegin() noexcept + { + return std::visit([this](auto& v) { return to_reverse_iterator(v.rbegin()); }, m_Data); + } + + const_reverse_iterator rbegin() const noexcept + { + return std::visit([this](auto& v) { return to_reverse_iterator(v.rbegin()); }, m_Data); + } + + reverse_iterator rend() noexcept + { + return std::visit([this](auto& v) { return to_reverse_iterator(v.rend()); }, m_Data); + } + + const_reverse_iterator rend() const noexcept + { + return std::visit([this](auto& v) { return to_reverse_iterator(v.rend()); }, m_Data); + } + + reference front() noexcept + { + return std::visit([](auto& v) -> reference { return v.front(); }, m_Data); + } + + const_reference front() const noexcept + { + return std::visit([](auto& v) -> const_reference { return v.front(); }, m_Data); + } + + reference back() noexcept + { + return std::visit([](auto& v) -> reference { return v.back(); }, m_Data); + } + + const_reference back() const noexcept + { + return std::visit([](auto& v) -> const_reference { return v.back(); }, m_Data); + } + + + /** + * @brief Returns the current size of the vector. + * @return The current size of the vector in number of elements. + */ + size_type size() const noexcept + { + return std::visit([](auto& v) { return v.size(); }, m_Data); + } + + /** + * @brief Returns the current capacity of the vector. + * @return The current capacity of the vector in number of elements. + */ + size_type capacity() const noexcept + { + return std::visit([](auto& v) { return v.capacity(); }, m_Data); + } + + /** + * @brief Returns true if the vector has no elements. + * @return Whether the vector has a size of 0. + */ + bool empty() const noexcept + { + return std::visit([](auto& v) { return v.empty(); }, m_Data); + } + + + /** + * @brief Returns an iterator to the start of the vector. + * @return An iterator to the start of the vector. + */ + iterator data() noexcept + { + return std::visit([](auto& v) { return v.data(); }, m_Data); + } + + /** + * @brief Returns a const iterator to the start of the vector. + * @return A const iterator to the start of the vector. + */ + const_iterator data() const noexcept + { + return std::visit([](auto& v) { return v.data(); }, m_Data); + } + + /** + * @brief Returns a reference to the element at @p index. + * @note An index higher than size() will produce undefined behaviour. + * @param index The index of the element in the vector. Must be less than size(). + * @return A reference to the element at @p index. + */ + reference at(size_t index) noexcept + { + return std::visit([&index](auto& v) -> reference { return v.at(index); }, m_Data); + } + + /** + * @brief Returns a reference to the element at @p index. + * @note An index higher than size() will produce undefined behaviour. + * @param index The index of the element in the vector. Must be less than size(). + * @return A reference to the element at @p index. + */ + const_reference at(size_t index) const noexcept + { + return std::visit([&index](auto& v) -> const_reference { return v.at(index); }, m_Data); + } + + + /** + * @brief Resizes the vector to the given size. + * @param n The size to resize to. + */ + void resize(size_t n) noexcept + { + move_if_growing(n); + + std::visit([&n](auto& v) { v.resize(n); }, m_Data); + } + + /** + * @brief Reserves the capacity of the vector to @p n, without initializing any elements. + * @param n The minimum capacity of the vector. + */ + void reserve(size_t n) noexcept + { + move_if_growing(n); + + if (m_Data.index() == 1) + { + std::get(m_Data).reserve(n); + } + } + + /** + * @brief Pushes a new element into the vector by copying it. + * @param value The element to copy into the vector. + */ + void push_back(const value_type& element) noexcept + { + move_if_growing(size() + 1); + + std::visit([&element](auto& v) { v.push_back(element); }, m_Data); + } + + /** + * @brief Pushes a new element into the vector by moving it. + * @param value The element to move into the vector. + */ + void push_back(value_type&& element) noexcept + { + move_if_growing(size() + 1); + + std::visit([&element](auto& v) { v.push_back(std::move(element)); }, m_Data); + } + + /** + * @brief Pushes a range of values into the vector. + * @param first A pointer to the first element. + * @param last A pointer one element past the last element. + */ + void assign(const_iterator first, const_iterator last) noexcept + { + move_if_growing(last - first); + + std::visit([&first, &last](auto& v) { v.assign(first, last); }, m_Data); + } + + /** + * @brief Pushes a new element into the vector by constructing it. + * @tparam ...Args Variadic template arguments. + * @param ...args Any arguments to use in the construction of the element. + * @return A reference to the element that was added. + */ + template + reference emplace_back(Args&&... args) noexcept + { + move_if_growing(size() + 1); + + return std::visit([&](auto& v) { return v.emplace_back(std::forward(args) ...); }, m_Data); + } + + /** + * @brief Inserts a new element into the vector by constructing it. + * @tparam ...Args Variadic template arguments. + * @param iter An iterator pointing to the location where the new element should be emplaced. + * @param ...args Any arguments to use in the construction of the element. + * @return An iterator to the element that was added. + */ + template + iterator emplace(const_iterator iter, Args&&... args) noexcept + { + move_if_growing(size() + 1); + + switch (m_Data.index()) + { + case 0: + return std::get(m_Data).emplace(iter, std::forward(args) ...); + case 1: + return to_inline_iterator(std::get(m_Data).emplace(to_vector_iterator(iter), std::forward(args) ...)); + default: + KTL_UNREACHABLE(); + } + } + + /** + * @brief Erases the element pointed to by the iterator. + * @param iter An iterator pointing to the element. + * @return An iterator pointing to the element immidiately after the erased one. + */ + iterator erase(const_iterator iter) noexcept + { + switch (m_Data.index()) + { + case 0: + return std::get(m_Data).erase(iter); + case 1: + return to_inline_iterator(std::get(m_Data).erase(to_vector_iterator(iter))); + default: + KTL_UNREACHABLE(); + } + } + + /** + * @brief Erases all elements in a range. + * @param first An iterator pointing to the first element. + * @param last An iterator pointing to the location after the last element. + * @return An iterator pointing to the element immidiately after the erased ones. + */ + iterator erase(const_iterator first, const_iterator last) noexcept + { + switch (m_Data.index()) + { + case 0: + return std::get(m_Data).erase(first, last); + case 1: + return to_inline_iterator(std::get(m_Data).erase(to_vector_iterator(first), to_vector_iterator(last))); + default: + KTL_UNREACHABLE(); + } + } + + /** + * @brief Removes the last element from the vector and returns it. + */ + void pop_back() noexcept + { + switch (m_Data.index()) + { + case 0: + std::get<0>(m_Data).pop_back(); + break; + case 1: + std::get<1>(m_Data).pop_back(); + break; + default: + KTL_UNREACHABLE(); + } + } + + /** + * @brief Clears all elements in the vector. + */ + void clear() noexcept + { + return std::visit([](auto& v) { return v.clear(); }, m_Data); + } + + private: + void move_if_growing(size_t n) noexcept + { + if (m_Data.index() == 0 && n > InlineCapacity) + { + size_type old_size = size(); + + Vec tmp{}; + tmp.reserve(n); + for (size_t i = 0; i < old_size; ++i) + { + tmp.push_back(std::move(at(i))); + } + + m_Data = std::move(tmp); + } + } + +#pragma region Inline iterators + iterator to_inline_iterator(iterator iter) noexcept + { + return iter; + } + + const_iterator to_inline_iterator(const_iterator iter) const noexcept + { + return iter; + } + + template>> + iterator to_inline_iterator(typename V::iterator iter) noexcept + { + size_type diff = std::distance(std::get(m_Data).begin(), iter); + + return std::get(m_Data).data() + diff; + } + + template>> + const_iterator to_inline_iterator(typename V::const_iterator iter) const noexcept + { + size_type diff = std::distance(std::get(m_Data).begin(), iter); + + return std::get(m_Data).data() + diff; + } +#pragma endregion + +#pragma region Vector iterators + typename Vec::const_iterator to_vector_iterator(const_iterator iter) const noexcept + { + size_type diff = iter - std::get(m_Data).data(); + + auto vec_iter = std::get(m_Data).begin(); + std::advance(vec_iter, diff); + + return vec_iter; + } +#pragma endregion + +#pragma region Inline reverse iterators + reverse_iterator to_reverse_iterator(reverse_iterator iter) noexcept + { + return iter; + } + + const_reverse_iterator to_reverse_iterator(const_reverse_iterator iter) const noexcept + { + return iter; + } + + template>> + reverse_iterator to_reverse_iterator(typename V::reverse_iterator iter) noexcept + { + difference_type diff = std::distance(std::get(m_Data).rbegin(), iter); + + auto inline_iter = std::reverse_iterator(to_inline_iterator(std::get(m_Data).end())); + std::advance(inline_iter, diff); + + return inline_iter; + } + + template>> + const_reverse_iterator to_reverse_iterator(typename V::const_reverse_iterator iter) const noexcept + { + difference_type diff = std::distance(std::get(m_Data).rbegin(), iter); + + auto inline_iter = std::reverse_iterator(to_inline_iterator(std::get(m_Data).end())); + std::advance(inline_iter, diff); + + return inline_iter; + } +#pragma endregion + + private: + variant_type m_Data; + }; +} \ No newline at end of file diff --git a/include/ktl/containers/sbo_vector_fwd.h b/include/ktl/containers/sbo_vector_fwd.h new file mode 100644 index 0000000..cea228e --- /dev/null +++ b/include/ktl/containers/sbo_vector_fwd.h @@ -0,0 +1,7 @@ +#pragma once + +namespace ktl +{ + template + class sbo_vector; +} \ No newline at end of file diff --git a/include/ktl/containers/trivial_vector.h b/include/ktl/containers/trivial_vector.h index 68d2986..6e5556b 100644 --- a/include/ktl/containers/trivial_vector.h +++ b/include/ktl/containers/trivial_vector.h @@ -12,7 +12,7 @@ namespace ktl { /** - * @brief A dynamically allocated vector or trivial types + * @brief A dynamically allocated vector of trivial types * @tparam T The type to use. Must be trivially copyable and default constructible * @tparam Alloc The type of allocoator to use */ @@ -23,14 +23,22 @@ namespace ktl static_assert(std::is_default_constructible::value, "Template class needs to be default constructible"); static_assert(std::is_trivially_copyable::value, "Template class needs to be trivially copyable"); - typedef std::allocator_traits Traits; + using Traits = std::allocator_traits; public: - typedef T* iterator; - typedef const T* const_iterator; - - typedef std::reverse_iterator reverse_iterator; - typedef std::reverse_iterator const_reverse_iterator; + using allocator_type = Alloc; + using value_type = T; + using size_type = size_t; + using difference_type = std::ptrdiff_t; + + using reference = T&; + using const_reference = const T&; + + using iterator = T*; + using const_iterator = const T*; + + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; public: /** @@ -204,6 +212,19 @@ namespace ktl return *this; } + friend bool operator==(const trivial_vector& lhs, const trivial_vector& rhs) noexcept + { + if (lhs.size() != rhs.size()) + return false; + + return std::memcmp(lhs.data(), rhs.data(), lhs.size()) == 0; + } + + friend bool operator!=(const trivial_vector& lhs, const trivial_vector& rhs) noexcept + { + return !(lhs == rhs); + } + /** * @brief Returns a reference to the element at @p index. * @note An index higher than size() will produce undefined behaviour. @@ -237,6 +258,14 @@ namespace ktl const_reverse_iterator rend() const noexcept { return std::reverse_iterator(m_Begin); } + T& front() noexcept { return *begin(); } + + const T& front() const noexcept { return *begin(); } + + T& back() noexcept { return *rbegin(); } + + const T& back() const noexcept { return *rbegin(); } + /** * @brief Returns the current size of the vector. @@ -356,13 +385,13 @@ namespace ktl * @return An iterator to the element that was added. */ template - iterator emplace_back(Args&&... args) noexcept + reference emplace_back(Args&&... args) noexcept { if (m_End == m_EndMax) expand(1); *m_End = T(std::forward(args)...); - return m_End++; + return *(m_End++); } /** @@ -373,17 +402,21 @@ namespace ktl * @return An iterator to the element that was added. */ template - void emplace(const_iterator iter, Args&&... args) noexcept + iterator emplace(const_iterator const_iter, Args&&... args) noexcept { - KTL_ASSERT(iter >= m_Begin && iter <= m_End); + KTL_ASSERT(const_iter >= m_Begin && const_iter <= m_End); + + T* iter = const_cast(const_iter); if (m_End == m_EndMax) expand(1); - std::memmove(const_cast(iter + 1), iter, (m_End - iter) * sizeof(T)); + std::memmove(iter + 1, iter, (m_End - iter) * sizeof(T)); *iter = T(std::forward(args)...); m_End++; + + return iter; } /** @@ -424,7 +457,7 @@ namespace ktl * @brief Removes the last element from the vector and returns it. * @return The last element in the vector. */ - T pop_back() noexcept { return m_Begin[--m_End]; } + T pop_back() noexcept { return *(--m_End); } /** * @brief Clears all elements in the vector. diff --git a/include/ktl/ktl.h b/include/ktl/ktl.h index 30a6041..a1a71a1 100644 --- a/include/ktl/ktl.h +++ b/include/ktl/ktl.h @@ -19,8 +19,10 @@ // Containers #include "containers/binary_heap.h" +#include "containers/inline_vector.h" #include "containers/ipair.h" #include "containers/packed_ptr.h" +#include "containers/sbo_vector.h" #include "containers/trivial_array.h" #include "containers/trivial_buffer.h" #include "containers/trivial_vector.h" \ No newline at end of file diff --git a/include/ktl/ktl_container_fwd.h b/include/ktl/ktl_container_fwd.h index a7c3aef..a1bbb8a 100644 --- a/include/ktl/ktl_container_fwd.h +++ b/include/ktl/ktl_container_fwd.h @@ -1,5 +1,7 @@ #pragma once #include "containers/binary_heap_fwd.h" +#include "containers/inline_vector_fwd.h" +#include "containers/sbo_vector_fwd.h" #include "containers/trivial_array_fwd.h" #include "containers/trivial_vector_fwd.h" \ No newline at end of file diff --git a/include/ktl/utility/unreachable.h b/include/ktl/utility/unreachable.h new file mode 100644 index 0000000..b9fb01c --- /dev/null +++ b/include/ktl/utility/unreachable.h @@ -0,0 +1,14 @@ +#pragma once + +#if defined(__cpp_lib_unreachable) && __cpp_lib_unreachable >= 202202L +#include +#define KTL_UNREACHABLE() std::unreachable() +#else + +#if defined(_MSC_VER) && !defined(__clang__) // MSVC +# define KTL_UNREACHABLE() __assume(false); +#else // GCC, Clang +# define KTL_UNREACHABLE() __builtin_unreachable(); +#endif + +#endif // __cpp_lib_unreachable \ No newline at end of file diff --git a/src/performance/sbo_vector_performance.cpp b/src/performance/sbo_vector_performance.cpp new file mode 100644 index 0000000..7c5c0d3 --- /dev/null +++ b/src/performance/sbo_vector_performance.cpp @@ -0,0 +1,31 @@ +#include "shared/profiler.h" +#include "shared/types.h" +#include "shared/vector_utility.h" + +#include "ktl/containers/sbo_vector.h" +#include "ktl/containers/trivial_vector.h" + +#include "ktl/allocators/mallocator.h" +#include "ktl/allocators/stack_allocator.h" + +namespace ktl::performance::sbo_vector +{ + void run_benchmark() + { + ktl::sbo_vector> vec{}; + + profiler::resume(); + + for (size_t i = 0; i < 1000; i++) + vec.push_back({ 42.0, 58.0 }); + + profiler::pause(); + } + + KTL_ADD_BENCHMARK(sbo_vector_push_std_allocator_trivial) + { + profiler::pause(); + + run_benchmark(); + } +} \ No newline at end of file diff --git a/src/shared/vector_utility.h b/src/shared/vector_utility.h index f2c1059..075070c 100644 --- a/src/shared/vector_utility.h +++ b/src/shared/vector_utility.h @@ -1,12 +1,18 @@ #pragma once + #include "assert_utility.h" +#include "random.h" #include "types.h" -#include + namespace ktl::test { template - void assert_vector_push_back(Vec& vec, const T* values, size_t amount) + void assert_vector_push_back(Vec& vec, T* values, size_t amount) { + // Allocate with random values + std::shuffle(values, values + amount, random_generator); + + // Push back in random order for (size_t i = 0; i < amount; ++i) vec.push_back(values[i]); @@ -23,12 +29,37 @@ namespace ktl::test KTL_TEST_ASSERT(element == values[counter++]); // Assert erase first element - for (size_t i = 0; i < amount; ++i) + for (size_t i = 0; i < amount / 2; ++i) { auto iter = vec.begin(); KTL_TEST_ASSERT(*iter == values[i]); KTL_TEST_ASSERT(vec.erase(iter) == vec.begin()); } + + KTL_TEST_ASSERT(vec.size() == amount / 2); + + // Assert operator[] + counter = amount; + for (auto iter = vec.rbegin(); iter != vec.rend(); ++iter) + KTL_TEST_ASSERT(*iter == values[--counter]); + + for (size_t i = 0; i < amount / 2; ++i) + KTL_TEST_ASSERT(vec[i] == values[amount / 2 + i]); + + // Assert erase last half at once + vec.erase(vec.begin(), vec.end() - 1); + + KTL_TEST_ASSERT(vec.size() == 1); + KTL_TEST_ASSERT(vec.back() == values[amount - 1]); + + // Assert emplace at begin + vec.emplace(vec.begin(), values[0]); + KTL_TEST_ASSERT(vec.size() == 2); + KTL_TEST_ASSERT(vec.front() == values[0]); + + // Assert pop_back + vec.pop_back(); + vec.pop_back(); // Assert size KTL_TEST_ASSERT(vec.begin() == vec.end()); @@ -37,6 +68,7 @@ namespace ktl::test vec.clear(); KTL_TEST_ASSERT(vec.size() == 0); + KTL_TEST_ASSERT(vec.empty()); } template diff --git a/src/test/inline_vector_test.cpp b/src/test/inline_vector_test.cpp new file mode 100644 index 0000000..d4b0f5e --- /dev/null +++ b/src/test/inline_vector_test.cpp @@ -0,0 +1,76 @@ +#include "shared/assert_utility.h" +#include "shared/construct_utility.h" +#include "shared/test.h" +#include "shared/types.h" +#include "shared/vector_utility.h" + +#include "ktl/ktl_alloc_fwd.h" + +#define KTL_DEBUG_ASSERT +#include "ktl/containers/inline_vector.h" + +// Naming scheme: test_inline_vector_[Alloc]_[Type] +// Contains tests that relate directly to the ktl::inline_vector + +namespace ktl::test::inline_vector +{ + KTL_ADD_TEST(test_inline_vector_construct) + { + constexpr size_t size = 4; + + using Container = ktl::inline_vector; + + double values[] + { + 4.0, + 8.0, + -1.0, + 10.0 + }; + + Container baseContainer; + + assert_construct_container( + [&](Container& lhs, Container& rhs) + { + // Comparison function + KTL_TEST_ASSERT(lhs == rhs); + }, [&]() + { + // Push some elements + for (size_t i = 0; i < size; i++) + baseContainer.push_back(values[i]); + + return baseContainer; + }, [&]() + { + // Construct using initializer list + return Container{ values[0], values[1], values[2], values[3] }; + }, [&]() + { + // Construct from pointer range + return Container(values, values + size); + }); + } + + KTL_ADD_TEST(test_inline_vector_double) + { + ktl::inline_vector vec; + + assert_vector_values(vec); + } + + KTL_ADD_TEST(test_inline_vector_trivial) + { + ktl::inline_vector vec; + + assert_vector_values(vec); + } + + KTL_ADD_TEST(test_inline_vector_complex) + { + ktl::inline_vector vec; + + assert_vector_values(vec); + } +} \ No newline at end of file diff --git a/src/test/sbo_vector_test.cpp b/src/test/sbo_vector_test.cpp new file mode 100644 index 0000000..c0ee8a0 --- /dev/null +++ b/src/test/sbo_vector_test.cpp @@ -0,0 +1,143 @@ +#include "shared/assert_utility.h" +#include "shared/construct_utility.h" +#include "shared/test.h" +#include "shared/types.h" +#include "shared/vector_utility.h" + +#define KTL_DEBUG_ASSERT +#include "ktl/ktl_alloc_fwd.h" + +#include "ktl/containers/sbo_vector.h" +#include "ktl/containers/trivial_vector.h" + +#include "ktl/allocators/linear_allocator.h" +#include "ktl/allocators/mallocator.h" +#include "ktl/allocators/shared.h" +#include "ktl/allocators/stack_allocator.h" +#include "ktl/allocators/type_allocator.h" + +#include + +// Naming scheme: test_sbo_vector_[Alloc]_[Type] +// Contains tests that relate directly to the ktl::sbo_vector + +namespace ktl::test::sbo_vector +{ + KTL_ADD_TEST(test_sbo_std_vector_construct) + { + using Container = ktl::sbo_vector>; + + constexpr size_t size = 4; + + double values[] = { + 4.0, + 8.0, + -1.0, + 10.0 + }; + + Container baseContainer; + + assert_construct_container( + [&](Container& lhs, Container& rhs) + { + KTL_TEST_ASSERT(lhs == rhs); + + // Comparison function + for (size_t i = 0; i < size; i++) + KTL_TEST_ASSERT(lhs[i] == rhs[i]); + }, + [&]() + { + // Push some elements + for (size_t i = 0; i < size; i++) + baseContainer.push_back(values[i]); + + return baseContainer; + }, + [&]() + { + // Construct using initializer list + return Container{ values[0], values[1], values[2], values[3] }; + }, + [&]() + { + // Construct from pointer range + return Container(values, values + size); + }); + } + + KTL_ADD_TEST(test_sbo_trivial_vector_construct) + { + using Container = ktl::sbo_vector>; + + constexpr size_t size = 4; + + double values[] = { + 4.0, + 8.0, + -1.0, + 10.0 + }; + + Container baseContainer; + + assert_construct_container( + [&](Container& lhs, Container& rhs) + { + KTL_TEST_ASSERT(lhs == rhs); + + // Comparison function + for (size_t i = 0; i < size; i++) + KTL_TEST_ASSERT(lhs[i] == rhs[i]); + }, + [&]() + { + // Push some elements + for (size_t i = 0; i < size; i++) + baseContainer.push_back(values[i]); + + return baseContainer; + }, + [&]() + { + // Construct using initializer list + return Container{ values[0], values[1], values[2], values[3] }; + }, + [&]() + { + // Construct from pointer range + return Container(values, values + size); + }); + } + + KTL_ADD_TEST(test_sbo_trivial_vector_double) + { + ktl::sbo_vector> vec; + assert_vector_values(vec); + } + + KTL_ADD_TEST(test_sbo_trivial_vector_trivial) + { + ktl::sbo_vector> vec; + assert_vector_values(vec); + } + + KTL_ADD_TEST(test_sbo_trivial_vector_packed) + { + ktl::sbo_vector> vec; + assert_vector_values(vec); + } + + KTL_ADD_TEST(test_sbo_std_vector_double) + { + ktl::sbo_vector> vec; + assert_vector_values(vec); + } + + KTL_ADD_TEST(test_sbo_std_vector_complex) + { + ktl::sbo_vector> vec; + assert_vector_values(vec); + } +} \ No newline at end of file diff --git a/src/test/trivial_vector_test.cpp b/src/test/trivial_vector_test.cpp index f63a75a..49d583b 100644 --- a/src/test/trivial_vector_test.cpp +++ b/src/test/trivial_vector_test.cpp @@ -42,8 +42,7 @@ namespace ktl::test::trivial_vector [&](Container& lhs, Container& rhs) { // Comparison function - for (size_t i = 0; i < size; i++) - KTL_TEST_ASSERT(lhs[i] == rhs[i]); + KTL_TEST_ASSERT(lhs == rhs); }, [&]() { // Push some elements