From f8c5b78f6d07d87b9f9bb145b5e7d44811ada05d Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Wed, 3 Apr 2024 05:17:15 +0200 Subject: [PATCH 01/10] Refactored buffer and buffer_view --- CMakeLists.txt | 1 + include/sparrow/buffer.hpp | 837 +++++++++++++++++++++-------- include/sparrow/buffer_view.hpp | 270 ++++++++++ include/sparrow/dynamic_bitset.hpp | 1 + test/test_buffer.cpp | 1 + 5 files changed, 898 insertions(+), 212 deletions(-) create mode 100644 include/sparrow/buffer_view.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index f16b8717..4a993257 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ set(SPARROW_HEADERS ${SPARROW_INCLUDE_DIR}/sparrow/allocator.hpp ${SPARROW_INCLUDE_DIR}/sparrow/array_data.hpp ${SPARROW_INCLUDE_DIR}/sparrow/buffer.hpp + ${SPARROW_INCLUDE_DIR}/sparrow/buffer_view.hpp ${SPARROW_INCLUDE_DIR}/sparrow/data_type.hpp ${SPARROW_INCLUDE_DIR}/sparrow/data_traits.hpp ${SPARROW_INCLUDE_DIR}/sparrow/dynamic_bitset.hpp diff --git a/include/sparrow/buffer.hpp b/include/sparrow/buffer.hpp index 1b828be2..053853ff 100644 --- a/include/sparrow/buffer.hpp +++ b/include/sparrow/buffer.hpp @@ -18,454 +18,867 @@ #include #include #include +#include +#include +#include "sparrow/allocator.hpp" #include "sparrow/iterator.hpp" #include "sparrow/mp_utils.hpp" namespace sparrow { + /** * @class buffer_base - * @brief Base class for buffer and buffer_view - * - * This class implements the common API for buffer and buffer_view. - * The only difference between these two inheriting classes is - * that buffer owns its memory while buffer_view only references it. + * @brief base class for buffer * - **/ + * This class provides memory management for the buffer class. + * The constructor and destructor perform allocation and deallocation + * of the internal storage, but do not initialize any element. + */ template class buffer_base { + protected: + + using allocator_type = any_allocator; + using alloc_traits = std::allocator_traits; + using pointer = typename alloc_traits::pointer; + using size_type = typename alloc_traits::size_type; + + struct buffer_data + { + pointer p_begin = nullptr; + pointer p_end = nullptr; + pointer p_storage_end = nullptr; + + buffer_data() = default; + constexpr buffer_data(buffer_data&&) noexcept; + constexpr buffer_data& operator=(buffer_data&&) noexcept; + }; + + buffer_base() = default; + + template + constexpr buffer_base(const A& a) noexcept; + + template + buffer_base(size_type n, const A& a = A()); + + template + constexpr buffer_base(pointer p, size_type n, const A& a = A()); + + ~buffer_base(); + + buffer_base(buffer_base&&) = default; + + template + constexpr buffer_base(buffer_base&& rhs, const A& a); + + constexpr allocator_type& get_allocator() noexcept; + constexpr const allocator_type& get_allocator() const noexcept; + constexpr buffer_data& get_data() noexcept; + constexpr const buffer_data& get_data() const noexcept; + + pointer allocate(size_type n); + void deallocate(pointer p, size_type n); + void create_storage(size_type n); + constexpr void assign_storage(pointer p, size_type n, size_type cap); + + private: + + allocator_type m_alloc; + buffer_data m_data; + }; + + /** + * @class buffer + * @brief Object that owns a piece of contiguous memory + * + * This class provides an API similat to std::vector, with + * two main differences: + * - it is not templated by the allocator type, but type-erases + * it instead. + * - it can acquire ownership of an already allocated raw buffer. + */ + template + class buffer : private buffer_base + { + using base_type = buffer_base; + using alloc_traits = typename base_type::alloc_traits; + + static_assert(std::same_as, T>, + "buffer must have a non-const, non-volatile, non-reference value_type"); public: - using self_type = buffer_base; + using allocator_type = typename base_type::allocator_type; using value_type = T; - using reference = T&; - using const_reference = const T&; - using pointer = T*; - using const_pointer = const T*; - using size_type = std::size_t; - using difference_type = std::ptrdiff_t; - + using reference = value_type&; + using const_reference = const value_type&; + using pointer = typename alloc_traits::pointer; + using const_pointer = typename alloc_traits::const_pointer; + using size_type = typename alloc_traits::size_type; + using difference_type = typename alloc_traits::difference_type; using iterator = pointer_iterator; using const_iterator = pointer_iterator; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; - bool empty() const noexcept; - size_type size() const noexcept; + buffer() = default; + + template + requires (not std::same_as> and allocator) + constexpr explicit buffer(const A& a); + + template + explicit buffer(size_type n, const A& a = A()); + + template + buffer(size_type n, const value_type& v, const A& a = A()); + + template + buffer(pointer p, size_type n, const A& a = A()); + + template + buffer(std::initializer_list init, const A& a = A()); + + template + buffer(It first, It last, const A& a = A()); + + ~buffer(); + + buffer(const buffer& rhs); + + template + buffer(const buffer& rhs, const A& a); + + buffer(buffer&& rhs) = default; - reference operator[](size_type); - const_reference operator[](size_type) const; + template + buffer(buffer&& rhs, const A& a); - reference front(); - const_reference front() const; + buffer& operator=(const buffer& rhs); + buffer& operator=(buffer&& rhs); + buffer& operator=(std::initializer_list init); - reference back(); - const_reference back() const; + // Element access + constexpr reference operator[](size_type i); + constexpr const_reference operator[](size_type i) const; + + constexpr reference front(); + constexpr const_reference front() const; + + constexpr reference back(); + constexpr const_reference back() const; + + // TODO: make this non template and make a buffer_caster class template - U* data() noexcept; + constexpr U* data() noexcept; + // TODO: make this non template and make a buffer_caster class template - const U* data() const noexcept; + constexpr const U* data() const noexcept; - iterator begin(); - iterator end(); + // Iterators - const_iterator begin() const; - const_iterator end() const; - const_iterator cbegin() const; - const_iterator cend() const; + constexpr iterator begin() noexcept; + constexpr iterator end() noexcept; - reverse_iterator rbegin(); - reverse_iterator rend(); + constexpr const_iterator begin() const noexcept; + constexpr const_iterator end() const noexcept; - const_reverse_iterator rbegin() const; - const_reverse_iterator rend() const; - const_reverse_iterator crbegin() const; - const_reverse_iterator crend() const; + constexpr const_iterator cbegin() const noexcept; + constexpr const_iterator cend() const noexcept; - void swap(buffer_base& rhs) noexcept; - bool equal(const buffer_base& rhs) const; + constexpr reverse_iterator rbegin() noexcept; + constexpr reverse_iterator rend() noexcept; - protected: + constexpr const_reverse_iterator rbegin() const noexcept; + constexpr const_reverse_iterator rend() const noexcept; - buffer_base() = default; - buffer_base(pointer p, size_type n); - ~buffer_base() = default; + constexpr const_reverse_iterator crbegin() const noexcept; + constexpr const_reverse_iterator crend() const noexcept; - buffer_base(const self_type&) = default; - self_type& operator=(const self_type&) = default; + // Capacity - buffer_base(self_type&&) = default; - self_type& operator=(self_type&&) = default; + constexpr bool empty() const noexcept; + constexpr size_type capacity() const noexcept; + constexpr size_type size() const noexcept; + constexpr size_type max_size() const noexcept; - void reset(pointer p, size_type n); + // Modifiers + + constexpr void clear(); + constexpr void reserve(size_type new_cap); + constexpr void resize(size_type new_size); + constexpr void resize(size_type new_size, const value_type& value); + constexpr void shrink_to_fit(); + constexpr void swap(buffer& rhs); private: - pointer p_data = nullptr; - size_type m_size = 0u; + using base_type::get_data; + using base_type::get_allocator; + + template + constexpr void resize_impl(size_type new_size, F&& initializer); + + template + constexpr void assign_range_impl(It first, It last, std::forward_iterator_tag); + + constexpr void erase_at_end(pointer p); + + static constexpr size_type + check_init_length(size_type n, const allocator_type& a); + + static constexpr size_type + max_size_impl(const allocator_type& a) noexcept; + + static constexpr pointer + default_initialize(pointer begin, size_type n, allocator_type& a); + + static constexpr pointer + fill_initialize(pointer begin, size_type n, const value_type& v, allocator_type& a); + + template + static constexpr pointer + copy_initialize(It first, It last, pointer begin, allocator_type& a); + + template + constexpr pointer + allocate_and_copy(size_type n, It first, It last); + + static constexpr void + destroy(pointer first, pointer last, allocator_type& a); }; template - bool operator==(const buffer_base& lhs, const buffer_base& rhs); + constexpr bool operator==(const buffer& lhs, const buffer& rhs) noexcept; + + + /****************************** + * buffer_base implementation * + ******************************/ - /** - * @class buffer - * @brief Object that owns a piece of contiguous memory - */ template - class buffer : public buffer_base + constexpr buffer_base::buffer_data::buffer_data(buffer_data&& rhs) noexcept + : p_begin(rhs.p_begin) + , p_end(rhs.p_end) + , p_storage_end(rhs.p_storage_end) { - public: + rhs.p_begin = nullptr; + rhs.p_end = nullptr; + rhs.p_storage_end = nullptr; + } - using base_type = buffer_base; - using value_type = typename base_type::value_type; - using pointer = typename base_type::pointer; - using size_type = typename base_type::size_type; + template + constexpr auto buffer_base::buffer_data::operator=(buffer_data&& rhs) noexcept -> buffer_data& + { + std::swap(p_begin, rhs.p_begin); + std::swap(p_end, rhs.p_end); + std::swap(p_storage_end, rhs.p_storage_end); + return *this; + } - buffer() = default; - explicit buffer(size_type size); - buffer(size_type size, value_type); - buffer(pointer data, size_type size); + template + template + constexpr buffer_base::buffer_base(const A& a) noexcept + : m_alloc(a) + { + } + + template + template + buffer_base::buffer_base(size_type n, const A& a) + : m_alloc(a) + { + create_storage(n); + } - ~buffer(); + template + template + constexpr buffer_base::buffer_base(pointer p, size_type n, const A& a) + : m_alloc(a) + { + assign_storage(p, n, n); + } + + template + buffer_base::~buffer_base() + { + deallocate(m_data.p_begin, (m_data.p_storage_end - m_data.p_begin)); + } - buffer(const buffer&); - buffer& operator=(const buffer&); + template + template + constexpr buffer_base::buffer_base(buffer_base&& rhs, const A& a) + : m_alloc(a) + , m_data(std::move(rhs.m_data)) + { + } - buffer(buffer&&); - buffer& operator=(buffer&&); + template + constexpr auto buffer_base::get_allocator() noexcept -> allocator_type& + { + return m_alloc; + } - void resize(size_type new_size); - void resize(size_type new_size, value_type value); - void clear(); + template + constexpr auto buffer_base::get_allocator() const noexcept -> const allocator_type& + { + return m_alloc; + } - private: + template + constexpr auto buffer_base::get_data() noexcept -> buffer_data& + { + return m_data; + } - pointer allocate(size_type size) const; - void deallocate(pointer mem) const; - }; + template + constexpr auto buffer_base::get_data() const noexcept -> const buffer_data& + { + return m_data; + } - /* - * @class buffer_view - * @brief Object that references but does not own a piece of contiguous memory - */ template - class buffer_view : public buffer_base + auto buffer_base::allocate(size_type n) -> pointer { - public: + return alloc_traits::allocate(m_alloc, n); + } - using base_type = buffer_base; - using pointer = typename base_type::pointer; - using size_type = typename base_type::size_type; + template + void buffer_base::deallocate(pointer p, size_type n) + { + alloc_traits::deallocate(m_alloc, p, n); + } + + template + void buffer_base::create_storage(size_type n) + { + m_data.p_begin = allocate(n); + m_data.p_end = m_data.p_begin + n; + m_data.p_storage_end = m_data.p_begin + n; + } - explicit buffer_view(buffer& buffer); - buffer_view(pointer data, size_type size); - ~buffer_view() = default; + template + constexpr void buffer_base::assign_storage(pointer p, size_type n, size_type cap) + { + m_data.p_begin = p; + m_data.p_end = p + n; + m_data.p_storage_end = p + cap; + } - buffer_view(const buffer_view&) = default; - buffer_view& operator=(const buffer_view&) = default; + /************************* + * buffer implementation * + *************************/ - buffer_view(buffer_view&&) = default; - buffer_view& operator=(buffer_view&&) = default; - }; + template + template + requires (not std::same_as> and allocator) + constexpr buffer::buffer(const A& a) + : base_type(a) + { + } - /****************************** - * buffer_base implementation * - ******************************/ + template + template + buffer::buffer(size_type n, const A& a) + : base_type(check_init_length(n, a), a) + { + get_data().p_end = default_initialize(get_data().p_begin, n, get_allocator()); + } + + template + template + buffer::buffer(size_type n, const value_type& v, const A& a) + : base_type(check_init_length(n, a), a) + { + get_data().p_end = fill_initialize(get_data().p_begin, n, v, get_allocator()); + } + + template + template + buffer::buffer(pointer p, size_type n, const A& a) + : base_type(p, check_init_length(n, a), a) + { + } + + template + template + buffer::buffer(std::initializer_list init, const A& a) + : base_type(check_init_length(init.size(), a), a) + { + get_data().p_end = copy_initialize + ( + init.begin(), + init.end(), + get_data().p_begin, + get_allocator() + ); + } + + template + template + buffer::buffer(It first, It last, const A& a) + : base_type(check_init_length(std::distance(first, last), a), a) + { + get_data().p_end = copy_initialize + ( + first, + last, + get_data().p_begin, + get_allocator() + ); + } + + template + buffer::~buffer() + { + destroy(get_data().p_begin, get_data().p_end, get_allocator()); + } + + template + buffer::buffer(const buffer& rhs) + : base_type(rhs.size(), rhs.get_allocator()) + { + get_data().p_end = copy_initialize + ( + rhs.begin(), + rhs.end(), + get_data().p_begin, + get_allocator() + ); + } template - buffer_base::buffer_base(pointer p, size_type n) - : p_data(p) - , m_size(n) + template + buffer::buffer(const buffer& rhs, const A& a) + : base_type(rhs.size(), a) { + get_data().m_end = copy_initialize + ( + rhs.begin(), + rhs.end(), + get_data().p_begin, + get_allocator() + ); + } + + template + template + buffer::buffer(buffer&& rhs, const A& a) + : base_type(a) + { + if (rhs.get_allocator() == get_allocator()) + { + get_data() = std::move(rhs.m_data); + } + else if (!rhs.empty()) + { + this->create_storage(rhs.size()); + get_data().p_end = copy_initialize + ( + rhs.begin(), + rhs.end(), + get_data().p_begin, + get_allocator() + ); + rhs.clear(); + } } template - void buffer_base::reset(pointer p, size_type n) + buffer& buffer::operator=(const buffer& rhs) { - p_data = p; - m_size = n; + if (std::addressof(rhs) != this) + { + // We assume that any_allocator nevers propagates on assign + assign_range_impl( + rhs.get_data().p_begin, + rhs.get_data().p_end, + std::random_access_iterator_tag() + ); + } + return *this; } template - bool buffer_base::empty() const noexcept + buffer& buffer::operator=(buffer&& rhs) { - return size() == size_type(0); + if (get_allocator() == rhs.get_allocator()) + { + get_data() = std::move(rhs.get_data()); + } + else + { + assign_range_impl( + std::make_move_iterator(rhs.begin()), + std::make_move_iterator(rhs.end()), + std::random_access_iterator_tag() + ); + rhs.clear(); + } + return *this; } template - auto buffer_base::size() const noexcept -> size_type + buffer& buffer::operator=(std::initializer_list init) { - return m_size; + assign_range_impl( + std::make_move_iterator(init.begin()), + std::make_move_iterator(init.end()), + std::random_access_iterator_tag() + ); + return *this; } template - auto buffer_base::operator[](size_type pos) -> reference + constexpr auto buffer::operator[](size_type i) -> reference { - assert(pos < size()); - return data()[pos]; + return get_data().p_begin[i]; } template - auto buffer_base::operator[](size_type pos) const -> const_reference + constexpr auto buffer::operator[](size_type i) const -> const_reference { - assert(pos < size()); - return data()[pos]; + return get_data().p_begin[i]; } template - auto buffer_base::front() -> reference + constexpr auto buffer::front() -> reference { - assert(!empty()); - return data()[0]; + return *(get_data().p_begin); } template - auto buffer_base::front() const -> const_reference + constexpr auto buffer::front() const -> const_reference { - assert(!empty()); - return data()[0]; + return *(get_data().p_begin); } template - auto buffer_base::back() -> reference + constexpr auto buffer::back() -> reference { - assert(!empty()); - return data()[m_size - 1]; + return *(get_data().p_end - 1); } template - auto buffer_base::back() const -> const_reference + constexpr auto buffer::back() const -> const_reference { - assert(!empty()); - return data()[m_size - 1]; + return *(get_data().p_end - 1); } template template - U* buffer_base::data() noexcept + constexpr U* buffer::data() noexcept { - return reinterpret_cast(p_data); + return reinterpret_cast(get_data().p_begin); } template template - const U* buffer_base::data() const noexcept + constexpr const U* buffer::data() const noexcept { - return reinterpret_cast(p_data); + return reinterpret_cast(get_data().p_begin); } template - auto buffer_base::begin() -> iterator + constexpr auto buffer::begin() noexcept -> iterator { - return iterator(p_data); + return make_pointer_iterator(get_data().p_begin); } template - auto buffer_base::end() -> iterator + constexpr auto buffer::end() noexcept -> iterator { - return iterator(p_data + m_size); + return make_pointer_iterator(get_data().p_end); } template - auto buffer_base::begin() const -> const_iterator + constexpr auto buffer::begin() const noexcept -> const_iterator { return cbegin(); } template - auto buffer_base::end() const -> const_iterator + constexpr auto buffer::end() const noexcept -> const_iterator { return cend(); } template - auto buffer_base::cbegin() const -> const_iterator + constexpr auto buffer::cbegin() const noexcept -> const_iterator { - return const_iterator(p_data); + return make_pointer_iterator(const_pointer(get_data().p_begin)); } template - auto buffer_base::cend() const -> const_iterator + constexpr auto buffer::cend() const noexcept -> const_iterator { - return const_iterator(p_data + m_size); + return make_pointer_iterator(const_pointer(get_data().p_end)); } template - auto buffer_base::rbegin() -> reverse_iterator + constexpr auto buffer::rbegin() noexcept -> reverse_iterator { return reverse_iterator(end()); } - + template - auto buffer_base::rend() -> reverse_iterator + constexpr auto buffer::rend() noexcept -> reverse_iterator { return reverse_iterator(begin()); } template - auto buffer_base::rbegin() const -> const_reverse_iterator + constexpr auto buffer::rbegin() const noexcept -> const_reverse_iterator { return crbegin(); } - + template - auto buffer_base::rend() const -> const_reverse_iterator + constexpr auto buffer::rend() const noexcept -> const_reverse_iterator { return crend(); } template - auto buffer_base::crbegin() const -> const_reverse_iterator + constexpr auto buffer::crbegin() const noexcept -> const_reverse_iterator { - return const_reverse_iterator(cend()); + return const_reverse_iterator(end()); } template - auto buffer_base::crend() const -> const_reverse_iterator + constexpr auto buffer::crend() const noexcept -> const_reverse_iterator { - return const_reverse_iterator(cbegin()); + return const_reverse_iterator(begin()); } template - void buffer_base::swap(buffer_base& rhs) noexcept + constexpr bool buffer::empty() const noexcept { - std::swap(p_data, rhs.p_data); - std::swap(m_size, rhs.m_size); + return get_data().p_begin == get_data().p_end; } - + template - bool buffer_base::equal(const buffer_base& rhs) const + constexpr auto buffer::capacity() const noexcept -> size_type { - return m_size == rhs.m_size && std::equal(p_data, p_data + m_size, rhs.p_data); + return static_cast(get_data().p_storage_end - get_data().p_begin); } template - bool operator==(const buffer_base& lhs, const buffer_base& rhs) + constexpr auto buffer::size() const noexcept -> size_type { - return lhs.equal(rhs); + return static_cast(get_data().p_end - get_data().p_begin); } - /************************* - * buffer implementation * - *************************/ - template - buffer::buffer(size_type size) - : base_type{allocate(size), size} + constexpr auto buffer::max_size() const noexcept -> size_type { + return max_size_impl(get_allocator()); } - + template - buffer::buffer(size_type size, value_type value) - : base_type{allocate(size), size} + constexpr void buffer::clear() { - std::fill_n(base_type::data(), size, value); + erase_at_end(get_data().p_begin); } template - buffer::buffer(pointer data, size_type size) - : base_type{data, size} + constexpr void buffer::reserve(size_type new_cap) { + if (new_cap > max_size()) + { + throw std::length_error("buffer::reserve called with new_cap > max_size()"); + } + if (new_cap > capacity()) + { + const size_type old_size = size(); + pointer tmp = allocate_and_copy( + new_cap, + std::make_move_iterator(get_data().p_begin), + std::make_move_iterator(get_data().p_end) + ); + destroy(get_data().p_begin, get_data().p_end, get_allocator()); + this->deallocate(get_data().p_begin, get_data().p_storage_end - get_data().p_begin); + this->assign_storage(tmp, old_size, new_cap); + } } - + template - buffer::~buffer() + constexpr void buffer::resize(size_type new_size) { - deallocate(base_type::data()); + resize_impl(new_size, [this](size_type nb_init) + { + get_data().p_end = default_initialize(get_data().p_end, nb_init, get_allocator()); + }); } - + template - buffer::buffer(const buffer& rhs) - : base_type{allocate(rhs.size()), rhs.size()} + constexpr void buffer::resize(size_type new_size, const value_type& value) { - std::copy(rhs.data(), rhs.data() + rhs.size(), base_type::data()); + resize_impl(new_size, [this, &value](size_type nb_init) + { + get_data().p_end = fill_initialize(get_data().p_end, nb_init, value, get_allocator()); + }); } template - buffer& buffer::operator=(const buffer& rhs) + constexpr void buffer::shrink_to_fit() { - if (this != &rhs) + if (capacity() != size()) { - buffer tmp(rhs); - base_type::swap(tmp); + buffer(std::make_move_iterator(begin()), std::make_move_iterator(end()), get_allocator()).swap(*this); } - return *this; + } + + template + constexpr void buffer::swap(buffer& rhs) + { + std::swap(this->get_data(), rhs.get_data()); } template - buffer::buffer(buffer&& rhs) - : base_type{rhs.data(), rhs.size()} + template + constexpr void buffer::resize_impl(size_type new_size, F&& initializer) + { + if (new_size > size()) + { + const size_t nb_init = new_size - size(); + if (new_size <= capacity()) + { + initializer(nb_init); + } + else + { + reserve(new_size); + initializer(nb_init); + } + } + else if (new_size < size()) + { + erase_at_end(get_data().p_begin + new_size); + } + } + + template + template + constexpr void buffer::assign_range_impl(It first, It last, std::forward_iterator_tag) { - rhs.reset(nullptr, 0u); + const size_type sz = size(); + const size_type len = std::distance(first, last); + if (len > capacity()) + { + check_init_length(len, get_allocator()); + pointer p = allocate_and_copy(len, first, last); + destroy(get_data().p_begin, get_data().p_end, get_allocator()); + this->deallocate(get_data().p_begin, capacity()); + this->assign_storage(p, len, len); + } + else if (sz >= len) + { + pointer p = std::copy(first, last, get_data().p_begin); + erase_at_end(p); + } + else + { + It mid = first; + std::advance(mid, sz); + std::copy(first, mid, get_data().p_begin); + get_data().p_end = copy_initialize(mid, last, get_data().p_end, get_allocator()); + } } template - buffer& buffer::operator=(buffer&& rhs) + constexpr void buffer::erase_at_end(pointer p) { - base_type::swap(rhs); - return *this; + destroy(p, get_data().p_end, get_allocator()); + get_data().p_end = p; } template - void buffer::resize(size_type n) + constexpr auto buffer::check_init_length(size_type n, const allocator_type& a) -> size_type { - // TODO: add capacity, resize if growing only and define a shrink_to_fit method - if (n != base_type::size()) + if (n > max_size_impl(a)) { - buffer tmp(n); - const size_type copy_size = std::min(base_type::size(), n); - std::copy(base_type::data(), base_type::data() + copy_size, tmp.data()); - base_type::swap(tmp); + throw std::length_error("cannot create buffer larger than max_size()"); } + return n; } template - void buffer::resize(size_type n, value_type value) + constexpr auto buffer::max_size_impl(const allocator_type& a) noexcept -> size_type + { + const size_type diff_max = std::numeric_limits::max(); + const size_type alloc_max = std::allocator_traits::max_size(a); + return (std::min)(diff_max, alloc_max); + } + + template + constexpr auto buffer::default_initialize(pointer begin, size_type n, allocator_type& a) -> pointer { - const size_type old_size = base_type::size(); - resize(n); - if (old_size < n) + pointer current = begin; + for (; n > 0; --n, ++current) { - std::fill(base_type::data() + old_size, base_type::data() + n, value); + alloc_traits::construct(a, current); } + return current; } template - void buffer::clear() + constexpr auto buffer::fill_initialize(pointer begin, size_type n, const value_type& v, allocator_type& a) -> pointer { - resize(size_type(0)); + pointer current = begin; + for (; n > 0; --n, ++current) + { + alloc_traits::construct(a, current, v); + } + return current; } template - auto buffer::allocate(size_type size) const -> pointer + template + constexpr auto buffer::copy_initialize(It first, It last, pointer begin, allocator_type& a) -> pointer { - return new T[size]; + pointer current = begin; + for (; first != last; ++first, ++current) + { + alloc_traits::construct(a, current, *first); + } + return current; } template - void buffer::deallocate(pointer mem) const + template + constexpr auto buffer::allocate_and_copy(size_type n, It first, It last) -> pointer { - delete[] mem; + pointer p = this->allocate(n); + try + { + copy_initialize(first, last, p, get_allocator()); + } + catch(...) + { + this->deallocate(p, n); + throw; + } + return p; } - /****************************** - * buffer_view implementation * - ******************************/ - template - buffer_view::buffer_view(buffer& buffer) - : base_type{buffer.data(), buffer.size()} + constexpr void buffer::destroy(pointer first, pointer last, allocator_type& a) { + for (; first != last; ++first) + { + alloc_traits::destroy(a, first); + } } template - buffer_view::buffer_view(pointer data, size_type size) - : base_type{data, size} + constexpr bool operator==(const buffer& lhs, const buffer& rhs) noexcept { + return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); } } diff --git a/include/sparrow/buffer_view.hpp b/include/sparrow/buffer_view.hpp new file mode 100644 index 00000000..fc39fdf0 --- /dev/null +++ b/include/sparrow/buffer_view.hpp @@ -0,0 +1,270 @@ +// Copyright 2024 Man Group Operations Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include "sparrow/buffer.hpp" + +namespace sparrow +{ + /* + * @class buffer_view + * @brief Object that references but does not own a piece of contiguous memory + */ + template + class buffer_view + { + public: + + using self_type = buffer_view; + using value_type = T; + using reference = T&; + using const_reference = const T&; + using pointer = T*; + using const_pointer = const T*; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + using iterator = pointer_iterator; + using const_iterator = pointer_iterator; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + explicit buffer_view(buffer& buffer); + buffer_view(pointer p, size_type n); + + bool empty() const noexcept; + size_type size() const noexcept; + + reference operator[](size_type); + const_reference operator[](size_type) const; + + reference front(); + const_reference front() const; + + reference back(); + const_reference back() const; + + template + U* data() noexcept; + + template + const U* data() const noexcept; + + iterator begin(); + iterator end(); + + const_iterator begin() const; + const_iterator end() const; + const_iterator cbegin() const; + const_iterator cend() const; + + reverse_iterator rbegin(); + reverse_iterator rend(); + + const_reverse_iterator rbegin() const; + const_reverse_iterator rend() const; + const_reverse_iterator crbegin() const; + const_reverse_iterator crend() const; + + void swap(buffer_view& rhs) noexcept; + bool equal(const buffer_view& rhs) const; + + private: + + pointer p_data = nullptr; + size_type m_size = 0u; + }; + + template + bool operator==(const buffer_view& lhs, const buffer_view& rhs); + + /****************************** + * buffer_view implementation * + ******************************/ + + template + buffer_view::buffer_view(buffer& buffer) + : p_data(buffer.data()) + , m_size(buffer.size()) + { + } + + template + buffer_view::buffer_view(pointer p, size_type n) + : p_data(p) + , m_size(n) + { + } + + template + bool buffer_view::empty() const noexcept + { + return size() == size_type(0); + } + + template + auto buffer_view::size() const noexcept -> size_type + { + return m_size; + } + + template + auto buffer_view::operator[](size_type pos) -> reference + { + assert(pos < size()); + return data()[pos]; + } + + template + auto buffer_view::operator[](size_type pos) const -> const_reference + { + assert(pos < size()); + return data()[pos]; + } + + template + auto buffer_view::front() -> reference + { + assert(!empty()); + return data()[0]; + } + + template + auto buffer_view::front() const -> const_reference + { + assert(!empty()); + return data()[0]; + } + + template + auto buffer_view::back() -> reference + { + assert(!empty()); + return data()[m_size - 1]; + } + + template + auto buffer_view::back() const -> const_reference + { + assert(!empty()); + return data()[m_size - 1]; + } + + template + template + U* buffer_view::data() noexcept + { + return reinterpret_cast(p_data); + } + + template + template + const U* buffer_view::data() const noexcept + { + return reinterpret_cast(p_data); + } + + template + auto buffer_view::begin() -> iterator + { + return iterator(p_data); + } + + template + auto buffer_view::end() -> iterator + { + return iterator(p_data + m_size); + } + + template + auto buffer_view::begin() const -> const_iterator + { + return cbegin(); + } + + template + auto buffer_view::end() const -> const_iterator + { + return cend(); + } + + template + auto buffer_view::cbegin() const -> const_iterator + { + return const_iterator(p_data); + } + + template + auto buffer_view::cend() const -> const_iterator + { + return const_iterator(p_data + m_size); + } + + template + auto buffer_view::rbegin() -> reverse_iterator + { + return reverse_iterator(end()); + } + + template + auto buffer_view::rend() -> reverse_iterator + { + return reverse_iterator(begin()); + } + + template + auto buffer_view::rbegin() const -> const_reverse_iterator + { + return crbegin(); + } + + template + auto buffer_view::rend() const -> const_reverse_iterator + { + return crend(); + } + + template + auto buffer_view::crbegin() const -> const_reverse_iterator + { + return const_reverse_iterator(cend()); + } + + template + auto buffer_view::crend() const -> const_reverse_iterator + { + return const_reverse_iterator(cbegin()); + } + + template + void buffer_view::swap(buffer_view& rhs) noexcept + { + std::swap(p_data, rhs.p_data); + std::swap(m_size, rhs.m_size); + } + + template + bool buffer_view::equal(const buffer_view& rhs) const + { + return m_size == rhs.m_size && std::equal(p_data, p_data + m_size, rhs.p_data); + } + + template + bool operator==(const buffer_view& lhs, const buffer_view& rhs) + { + return lhs.equal(rhs); + } +} + diff --git a/include/sparrow/dynamic_bitset.hpp b/include/sparrow/dynamic_bitset.hpp index 7daff465..78de7c25 100644 --- a/include/sparrow/dynamic_bitset.hpp +++ b/include/sparrow/dynamic_bitset.hpp @@ -20,6 +20,7 @@ #include #include "sparrow/buffer.hpp" +#include "sparrow/buffer_view.hpp" #include "sparrow/mp_utils.hpp" namespace sparrow diff --git a/test/test_buffer.cpp b/test/test_buffer.cpp index 298cd6f4..5a785789 100644 --- a/test/test_buffer.cpp +++ b/test/test_buffer.cpp @@ -15,6 +15,7 @@ #include #include "sparrow/buffer.hpp" +#include "sparrow/buffer_view.hpp" #include "doctest/doctest.h" From ccdb69caf8caef5b28108a4b22e42f08faeb5b8d Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Wed, 10 Apr 2024 23:11:54 +0200 Subject: [PATCH 02/10] Fixed clang build issue --- include/sparrow/buffer.hpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/include/sparrow/buffer.hpp b/include/sparrow/buffer.hpp index 053853ff..fc8f6bb0 100644 --- a/include/sparrow/buffer.hpp +++ b/include/sparrow/buffer.hpp @@ -128,7 +128,10 @@ namespace sparrow template requires (not std::same_as> and allocator) - constexpr explicit buffer(const A& a); + constexpr explicit buffer(const A& a) + : base_type(a) + { + } template explicit buffer(size_type n, const A& a = A()); @@ -374,14 +377,6 @@ namespace sparrow * buffer implementation * *************************/ - template - template - requires (not std::same_as> and allocator) - constexpr buffer::buffer(const A& a) - : base_type(a) - { - } - template template buffer::buffer(size_type n, const A& a) From 708a5248d383de138283f72b0c5bfd1b43d81f3e Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Wed, 10 Apr 2024 23:28:35 +0200 Subject: [PATCH 03/10] More tests --- test/test_buffer.cpp | 173 +++++++++++++++++++++++++++++-------------- 1 file changed, 119 insertions(+), 54 deletions(-) diff --git a/test/test_buffer.cpp b/test/test_buffer.cpp index 5a785789..a666497c 100644 --- a/test/test_buffer.cpp +++ b/test/test_buffer.cpp @@ -70,6 +70,20 @@ namespace sparrow { CHECK_EQ(b3[i], expected_value); } + + buffer_test_type b4 = { 1u, 3u, 5u }; + CHECK_EQ(b4.size(), 3u); + CHECK_EQ(b4[0], 1u); + CHECK_EQ(b4[1], 3u); + CHECK_EQ(b4[2], 5u); + + std::vector exp = { 2u, 4u, 6u }; + buffer_test_type b5(exp.cbegin(), exp.cend()); + CHECK_EQ(b5.size(), exp.size()); + for (size_t i = 0; i < 3u; ++i) + { + CHECK_EQ(b5[i], exp[i]); + } } TEST_CASE("copy semantic") @@ -105,15 +119,7 @@ namespace sparrow CHECK_EQ(b4, control); } - TEST_CASE("empty") - { - buffer_test_type b1; - CHECK(b1.empty()); - - const std::size_t size = 4u; - buffer_test_type b2(make_test_buffer(size), size); - CHECK(!b2.empty()); - } + // Element access TEST_CASE("operator[]") { @@ -162,32 +168,87 @@ namespace sparrow CHECK_EQ(b3.data()[idx], expected_value); } - TEST_CASE("equality comparison") + // Iterators + + TEST_CASE("iterator") { + const std::size_t size = 8u; + buffer_test_type b(make_test_buffer(size), size); + auto iter = b.begin(); + auto citer = b.cbegin(); + for (std::size_t i = 0; i < b.size(); ++i) + { + CHECK_EQ(*iter++, b[i]); + CHECK_EQ(*citer++, b[i]); + } + CHECK_EQ(iter, b.end()); + CHECK_EQ(citer, b.cend()); + } + + TEST_CASE("reverse_iterator") + { + const std::size_t size = 8u; + buffer_test_type b(make_test_buffer(size), size); + auto iter = b.rbegin(); + auto citer = b.crbegin(); + for (std::size_t i = b.size(); i != 0u; --i) + { + CHECK_EQ(*iter++, b[i - 1]); + CHECK_EQ(*citer++, b[i - 1]); + } + CHECK_EQ(iter, b.rend()); + CHECK_EQ(citer, b.crend()); + } + + // capacity + + TEST_CASE("empty") + { + buffer_test_type b1; + CHECK(b1.empty()); + const std::size_t size = 4u; - buffer_test_type b1(make_test_buffer(size), size); buffer_test_type b2(make_test_buffer(size), size); - CHECK(b1 == b2); + CHECK(!b2.empty()); + } - const std::size_t size2 = 8u; - buffer_test_type b3(make_test_buffer(size2), size2); - CHECK(b1 != b3); + TEST_CASE("capacity") + { + const std::size_t size = 4u; + buffer_test_type b(make_test_buffer(size), size); + CHECK_EQ(b.capacity(), size); } - TEST_CASE("swap") + TEST_CASE("size") + { + const std::size_t size = 4u; + buffer_test_type b(make_test_buffer(size), size); + CHECK_EQ(b.size(), size); + } + + // modifiers + + TEST_CASE("clear") { const std::size_t size1 = 4u; - const std::size_t size2 = 8u; + buffer_test_type b(make_test_buffer(size1), size1); + b.clear(); + CHECK_EQ(b.size(), 0u); + } + TEST_CASE("reserve") + { + const std::size_t size1 = 4u; buffer_test_type b1(make_test_buffer(size1), size1); - buffer_test_type b2(make_test_buffer(size2), size2); - auto* data1 = b1.data(); - auto* data2 = b2.data(); - b1.swap(b2); - CHECK_EQ(b1.size(), size2); - CHECK_EQ(b1.data(), data2); - CHECK_EQ(b2.size(), size1); - CHECK_EQ(b2.data(), data1); + buffer_test_type b2(b1); + const std::size_t new_cap = 8u; + b1.reserve(new_cap); + CHECK_EQ(b1.capacity(), new_cap); + for (std::size_t i = 0; i < size1; ++i) + { + CHECK_EQ(b1.data()[i], b2.data()[i]); + } + } TEST_CASE("resize") @@ -204,47 +265,51 @@ namespace sparrow CHECK_EQ(b.data()[2], 2); const std::size_t size3 = 6u; - b.resize(size3); + const buffer_test_type::value_type v = 7u; + b.resize(size3, v); CHECK_EQ(b.size(), size3); CHECK_EQ(b.data()[2], 2); + CHECK_EQ(b.data()[4], v); + CHECK_EQ(b.data()[5], v); } - TEST_CASE("clear") + TEST_CASE("shrink_to_fit") { const std::size_t size1 = 4u; - buffer_test_type b(make_test_buffer(size1), size1); - b.clear(); - CHECK_EQ(b.size(), 0u); + buffer_test_type b1(make_test_buffer(size1), size1); + const std::size_t new_cap = 8u; + b1.reserve(new_cap); + CHECK_EQ(b1.capacity(), new_cap); + b1.shrink_to_fit(); + CHECK_EQ(b1.capacity(), size1); } - TEST_CASE("iterator") + TEST_CASE("swap") { - const std::size_t size = 8u; - buffer_test_type b(make_test_buffer(size), size); - auto iter = b.begin(); - auto citer = b.cbegin(); - for (std::size_t i = 0; i < b.size(); ++i) - { - CHECK_EQ(*iter++, b[i]); - CHECK_EQ(*citer++, b[i]); - } - CHECK_EQ(iter, b.end()); - CHECK_EQ(citer, b.cend()); + const std::size_t size1 = 4u; + const std::size_t size2 = 8u; + + buffer_test_type b1(make_test_buffer(size1), size1); + buffer_test_type b2(make_test_buffer(size2), size2); + auto* data1 = b1.data(); + auto* data2 = b2.data(); + b1.swap(b2); + CHECK_EQ(b1.size(), size2); + CHECK_EQ(b1.data(), data2); + CHECK_EQ(b2.size(), size1); + CHECK_EQ(b2.data(), data1); } - TEST_CASE("reverse_iterator") + TEST_CASE("equality comparison") { - const std::size_t size = 8u; - buffer_test_type b(make_test_buffer(size), size); - auto iter = b.rbegin(); - auto citer = b.crbegin(); - for (std::size_t i = b.size(); i != 0u; --i) - { - CHECK_EQ(*iter++, b[i - 1]); - CHECK_EQ(*citer++, b[i - 1]); - } - CHECK_EQ(iter, b.rend()); - CHECK_EQ(citer, b.crend()); + const std::size_t size = 4u; + buffer_test_type b1(make_test_buffer(size), size); + buffer_test_type b2(make_test_buffer(size), size); + CHECK(b1 == b2); + + const std::size_t size2 = 8u; + buffer_test_type b3(make_test_buffer(size2), size2); + CHECK(b1 != b3); } } From 179d66222e7c587b41101a87ec267d8f17a52b0b Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Thu, 11 Apr 2024 09:06:12 +0200 Subject: [PATCH 04/10] Static methods comment --- include/sparrow/buffer.hpp | 50 ++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/include/sparrow/buffer.hpp b/include/sparrow/buffer.hpp index fc8f6bb0..83f84568 100644 --- a/include/sparrow/buffer.hpp +++ b/include/sparrow/buffer.hpp @@ -232,6 +232,18 @@ namespace sparrow constexpr void erase_at_end(pointer p); + template + constexpr pointer + allocate_and_copy(size_type n, It first, It last); + + // The following methods are static because: + // - they accept an allocator argument, and do not depend on + // the state of buffer + // - taking an allocator argument instead of relying on get_allocator + // will make it easier to support allocators that propagate + // on copy / move, as these methods will be called with allocators + // from different instances of buffers. + static constexpr size_type check_init_length(size_type n, const allocator_type& a); @@ -248,10 +260,6 @@ namespace sparrow static constexpr pointer copy_initialize(It first, It last, pointer begin, allocator_type& a); - template - constexpr pointer - allocate_and_copy(size_type n, It first, It last); - static constexpr void destroy(pointer first, pointer last, allocator_type& a); }; @@ -793,6 +801,23 @@ namespace sparrow get_data().p_end = p; } + template + template + constexpr auto buffer::allocate_and_copy(size_type n, It first, It last) -> pointer + { + pointer p = this->allocate(n); + try + { + copy_initialize(first, last, p, get_allocator()); + } + catch(...) + { + this->deallocate(p, n); + throw; + } + return p; + } + template constexpr auto buffer::check_init_length(size_type n, const allocator_type& a) -> size_type { @@ -845,23 +870,6 @@ namespace sparrow return current; } - template - template - constexpr auto buffer::allocate_and_copy(size_type n, It first, It last) -> pointer - { - pointer p = this->allocate(n); - try - { - copy_initialize(first, last, p, get_allocator()); - } - catch(...) - { - this->deallocate(p, n); - throw; - } - return p; - } - template constexpr void buffer::destroy(pointer first, pointer last, allocator_type& a) { From f09d41bec258bf1978d39b6feb54a7cecf0abaa2 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Mon, 15 Apr 2024 14:29:55 +0200 Subject: [PATCH 05/10] Changes according to review --- include/sparrow/buffer.hpp | 32 ++++++++++++++++---------------- test/test_buffer.cpp | 1 - 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/include/sparrow/buffer.hpp b/include/sparrow/buffer.hpp index 83f84568..9ae7caf3 100644 --- a/include/sparrow/buffer.hpp +++ b/include/sparrow/buffer.hpp @@ -95,10 +95,10 @@ namespace sparrow * @class buffer * @brief Object that owns a piece of contiguous memory * - * This class provides an API similat to std::vector, with + * This class provides an API similar to std::vector, with * two main differences: - * - it is not templated by the allocator type, but type-erases - * it instead. + * - it is not templated by the allocator type, but makes use of + * any_allocator which type-erases it. * - it can acquire ownership of an already allocated raw buffer. */ template @@ -134,19 +134,19 @@ namespace sparrow } template - explicit buffer(size_type n, const A& a = A()); + constexpr explicit buffer(size_type n, const A& a = A()); template - buffer(size_type n, const value_type& v, const A& a = A()); + constexpr buffer(size_type n, const value_type& v, const A& a = A()); template - buffer(pointer p, size_type n, const A& a = A()); + constexpr buffer(pointer p, size_type n, const A& a = A()); template - buffer(std::initializer_list init, const A& a = A()); + constexpr buffer(std::initializer_list init, const A& a = A()); template - buffer(It first, It last, const A& a = A()); + constexpr buffer(It first, It last, const A& a = A()); ~buffer(); @@ -267,7 +267,6 @@ namespace sparrow template constexpr bool operator==(const buffer& lhs, const buffer& rhs) noexcept; - /****************************** * buffer_base implementation * ******************************/ @@ -376,6 +375,7 @@ namespace sparrow template constexpr void buffer_base::assign_storage(pointer p, size_type n, size_type cap) { + assert(n <= cap); m_data.p_begin = p; m_data.p_end = p + n; m_data.p_storage_end = p + cap; @@ -387,7 +387,7 @@ namespace sparrow template template - buffer::buffer(size_type n, const A& a) + constexpr buffer::buffer(size_type n, const A& a) : base_type(check_init_length(n, a), a) { get_data().p_end = default_initialize(get_data().p_begin, n, get_allocator()); @@ -395,7 +395,7 @@ namespace sparrow template template - buffer::buffer(size_type n, const value_type& v, const A& a) + constexpr buffer::buffer(size_type n, const value_type& v, const A& a) : base_type(check_init_length(n, a), a) { get_data().p_end = fill_initialize(get_data().p_begin, n, v, get_allocator()); @@ -403,14 +403,14 @@ namespace sparrow template template - buffer::buffer(pointer p, size_type n, const A& a) + constexpr buffer::buffer(pointer p, size_type n, const A& a) : base_type(p, check_init_length(n, a), a) { } template template - buffer::buffer(std::initializer_list init, const A& a) + constexpr buffer::buffer(std::initializer_list init, const A& a) : base_type(check_init_length(init.size(), a), a) { get_data().p_end = copy_initialize @@ -424,7 +424,7 @@ namespace sparrow template template - buffer::buffer(It first, It last, const A& a) + constexpr buffer::buffer(It first, It last, const A& a) : base_type(check_init_length(std::distance(first, last), a), a) { get_data().p_end = copy_initialize @@ -460,7 +460,7 @@ namespace sparrow buffer::buffer(const buffer& rhs, const A& a) : base_type(rhs.size(), a) { - get_data().m_end = copy_initialize + get_data().p_end = copy_initialize ( rhs.begin(), rhs.end(), @@ -497,7 +497,7 @@ namespace sparrow { if (std::addressof(rhs) != this) { - // We assume that any_allocator nevers propagates on assign + // We assume that any_allocator never propagates on assign assign_range_impl( rhs.get_data().p_begin, rhs.get_data().p_end, diff --git a/test/test_buffer.cpp b/test/test_buffer.cpp index a666497c..5f5ee2c1 100644 --- a/test/test_buffer.cpp +++ b/test/test_buffer.cpp @@ -248,7 +248,6 @@ namespace sparrow { CHECK_EQ(b1.data()[i], b2.data()[i]); } - } TEST_CASE("resize") From b50417bef8f2920058b326116ead5a1b2e214df1 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 16 Apr 2024 17:16:54 +0200 Subject: [PATCH 06/10] Changes according to second review --- include/sparrow/buffer.hpp | 16 ++++++++-------- include/sparrow/buffer_view.hpp | 16 +++++++--------- test/test_buffer.cpp | 6 ++++++ 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/include/sparrow/buffer.hpp b/include/sparrow/buffer.hpp index 9ae7caf3..9563d62c 100644 --- a/include/sparrow/buffer.hpp +++ b/include/sparrow/buffer.hpp @@ -63,7 +63,7 @@ namespace sparrow constexpr buffer_base(const A& a) noexcept; template - buffer_base(size_type n, const A& a = A()); + constexpr buffer_base(size_type n, const A& a = A()); template constexpr buffer_base(pointer p, size_type n, const A& a = A()); @@ -80,9 +80,9 @@ namespace sparrow constexpr buffer_data& get_data() noexcept; constexpr const buffer_data& get_data() const noexcept; - pointer allocate(size_type n); - void deallocate(pointer p, size_type n); - void create_storage(size_type n); + constexpr pointer allocate(size_type n); + constexpr void deallocate(pointer p, size_type n); + constexpr void create_storage(size_type n); constexpr void assign_storage(pointer p, size_type n, size_type cap); private: @@ -300,7 +300,7 @@ namespace sparrow template template - buffer_base::buffer_base(size_type n, const A& a) + constexpr buffer_base::buffer_base(size_type n, const A& a) : m_alloc(a) { create_storage(n); @@ -353,19 +353,19 @@ namespace sparrow } template - auto buffer_base::allocate(size_type n) -> pointer + constexpr auto buffer_base::allocate(size_type n) -> pointer { return alloc_traits::allocate(m_alloc, n); } template - void buffer_base::deallocate(pointer p, size_type n) + constexpr void buffer_base::deallocate(pointer p, size_type n) { alloc_traits::deallocate(m_alloc, p, n); } template - void buffer_base::create_storage(size_type n) + constexpr void buffer_base::create_storage(size_type n) { m_data.p_begin = allocate(n); m_data.p_end = m_data.p_begin + n; diff --git a/include/sparrow/buffer_view.hpp b/include/sparrow/buffer_view.hpp index fc39fdf0..cb7c32ab 100644 --- a/include/sparrow/buffer_view.hpp +++ b/include/sparrow/buffer_view.hpp @@ -20,7 +20,7 @@ namespace sparrow { /* * @class buffer_view - * @brief Object that references but does not own a piece of contiguous memory + * @brief Non-owning view of a contiguous sequence of objects of type T. */ template class buffer_view @@ -43,6 +43,11 @@ namespace sparrow explicit buffer_view(buffer& buffer); buffer_view(pointer p, size_type n); + // TODO: To be complete we also need a subrange(...) function + // and a constructor that takes a pair of iterators (they should + // be template parameters constrained by concept, not buffer_view::iterator). + // These are often used with view types (see std::span, which also provides + // first and last subview functions). bool empty() const noexcept; size_type size() const noexcept; @@ -79,7 +84,6 @@ namespace sparrow const_reverse_iterator crend() const; void swap(buffer_view& rhs) noexcept; - bool equal(const buffer_view& rhs) const; private: @@ -255,16 +259,10 @@ namespace sparrow std::swap(m_size, rhs.m_size); } - template - bool buffer_view::equal(const buffer_view& rhs) const - { - return m_size == rhs.m_size && std::equal(p_data, p_data + m_size, rhs.p_data); - } - template bool operator==(const buffer_view& lhs, const buffer_view& rhs) { - return lhs.equal(rhs); + return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); } } diff --git a/test/test_buffer.cpp b/test/test_buffer.cpp index 5f5ee2c1..641196ae 100644 --- a/test/test_buffer.cpp +++ b/test/test_buffer.cpp @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include #include +#include #include "sparrow/buffer.hpp" #include "sparrow/buffer_view.hpp" @@ -170,6 +172,10 @@ namespace sparrow // Iterators + static_assert(std::ranges::contiguous_range); + static_assert(std::contiguous_iterator); + static_assert(std::contiguous_iterator); + TEST_CASE("iterator") { const std::size_t size = 8u; From 4172e603d129cac27d21b902084ef6a79b966ac2 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Tue, 16 Apr 2024 17:24:07 +0200 Subject: [PATCH 07/10] Added iterator consistency test --- test/test_buffer.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/test_buffer.cpp b/test/test_buffer.cpp index 641196ae..ef53a7a1 100644 --- a/test/test_buffer.cpp +++ b/test/test_buffer.cpp @@ -206,6 +206,40 @@ namespace sparrow CHECK_EQ(citer, b.crend()); } + TEST_CASE("iterator_consistency") + { + const std::size_t size = 8u; + buffer_test_type b(make_test_buffer(size), size); + + { + auto iter = --b.end(); + auto riter = b.rbegin(); + auto citer = --b.cend(); + auto criter = b.crbegin(); + + while (iter != b.begin()) + { + CHECK_EQ(*iter, *riter); + CHECK_EQ(*citer, *criter); + --iter, --citer, ++riter, ++criter; + } + } + + { + auto iter = b.begin(); + auto riter = --b.rend(); + auto citer = b.cbegin(); + auto criter = --b.crend(); + + while (iter != b.end()) + { + CHECK_EQ(*iter, *riter); + CHECK_EQ(*citer, *criter); + ++iter, ++citer, --riter, --criter; + } + } + } + // capacity TEST_CASE("empty") From c1fb2d213b74aabb7cc2c5bbc2f7ae69d3c06370 Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Wed, 17 Apr 2024 09:54:31 +0200 Subject: [PATCH 08/10] formatting --- include/sparrow/allocator.hpp | 182 ++++++++++++++++++++------------ include/sparrow/buffer.hpp | 138 ++++++++++-------------- include/sparrow/buffer_view.hpp | 1 - test/test_allocator.cpp | 12 ++- test/test_buffer.cpp | 4 +- 5 files changed, 178 insertions(+), 159 deletions(-) diff --git a/include/sparrow/allocator.hpp b/include/sparrow/allocator.hpp index ae514081..3ef93af4 100644 --- a/include/sparrow/allocator.hpp +++ b/include/sparrow/allocator.hpp @@ -17,8 +17,8 @@ #include #include #include -#include #include +#include #include namespace sparrow @@ -29,16 +29,19 @@ namespace sparrow */ template concept allocator = std::copy_constructible> - and std::move_constructible> - and std::equality_comparable> - and requires(std::remove_cvref_t& alloc, - typename std::remove_cvref_t::value_type* p, - std::size_t n) - { - typename std::remove_cvref_t::value_type; - { alloc.allocate(n) } -> std::same_as::value_type*>; - { alloc.deallocate(p, n) } -> std::same_as; - }; + and std::move_constructible> + and std::equality_comparable> + and requires( + std::remove_cvref_t& alloc, + typename std::remove_cvref_t::value_type* p, + std::size_t n + ) { + typename std::remove_cvref_t::value_type; + { + alloc.allocate(n) + } -> std::same_as::value_type*>; + { alloc.deallocate(p, n) } -> std::same_as; + }; /* * When the allocator A with value_type T satisfies this concept, any_allocator @@ -46,8 +49,9 @@ namespace sparrow * (Small Buffer Optimization). */ template - concept can_any_allocator_sbo = allocator && - (std::same_as> || std::same_as>); + concept can_any_allocator_sbo = allocator + && (std::same_as> + || std::same_as>); /* * Type erasure class for allocators. This allows to use any kind of allocator @@ -71,18 +75,17 @@ namespace sparrow template any_allocator(A&& alloc) - requires (not std::same_as, any_allocator> - and sparrow::allocator) + requires(not std::same_as, any_allocator> and sparrow::allocator) : m_storage(make_storage(std::forward(alloc))) { } [[nodiscard]] T* allocate(std::size_t n); void deallocate(T* p, std::size_t n); - + any_allocator select_on_container_copy_construction() const; - bool equal(const any_allocator& rhs) const; + bool equal(const any_allocator& rhs) const; private: @@ -100,7 +103,10 @@ namespace sparrow { A m_alloc; - explicit impl(A alloc) : m_alloc(std::move(alloc)) {} + explicit impl(A alloc) + : m_alloc(std::move(alloc)) + { + } [[nodiscard]] T* allocate(std::size_t n) override { @@ -127,12 +133,8 @@ namespace sparrow } }; - using storage_type = std::variant - < - std::allocator, - std::pmr::polymorphic_allocator, - std::unique_ptr - >; + using storage_type = std:: + variant, std::pmr::polymorphic_allocator, std::unique_ptr>; template std::unique_ptr make_storage(A&& alloc) const @@ -141,35 +143,56 @@ namespace sparrow } template - requires can_any_allocator_sbo + requires can_any_allocator_sbo A&& make_storage(A&& alloc) const { return std::forward(alloc); } - template struct overloaded : Ts... { using Ts::operator()...; }; + template + struct overloaded : Ts... + { + using Ts::operator()...; + }; // Although not required in C++20, clang needs it to build the code below - template overloaded(Ts...) -> overloaded; - + template + overloaded(Ts...) -> overloaded; + storage_type copy_storage(const storage_type& rhs) const { - return std::visit(overloaded { - [](const auto& arg) -> storage_type { return { std::decay_t(arg) }; }, - [](const std::unique_ptr& arg) -> storage_type { return { arg->clone() }; } - }, rhs); + return std::visit( + overloaded{ + [](const auto& arg) -> storage_type + { + return {std::decay_t(arg)}; + }, + [](const std::unique_ptr& arg) -> storage_type + { + return {arg->clone()}; + } + }, + rhs + ); } template decltype(auto) visit_storage(F&& f) { - return std::visit([&f](auto&& arg) - { - using A = std::decay_t; - if constexpr (can_any_allocator_sbo) - return f(arg); - else - return f(*arg); - }, m_storage); + return std::visit( + [&f](auto&& arg) + { + using A = std::decay_t; + if constexpr (can_any_allocator_sbo) + { + return f(arg); + } + else + { + return f(*arg); + } + }, + m_storage + ); } storage_type m_storage; @@ -194,13 +217,23 @@ namespace sparrow template [[nodiscard]] T* any_allocator::allocate(std::size_t n) { - return visit_storage([n](auto& allocator) { return allocator.allocate(n); }); + return visit_storage( + [n](auto& allocator) + { + return allocator.allocate(n); + } + ); } template void any_allocator::deallocate(T* p, std::size_t n) { - return visit_storage([n, p](auto& allocator) { return allocator.deallocate(p, n); }); + return visit_storage( + [n, p](auto& allocator) + { + return allocator.deallocate(p, n); + } + ); } template @@ -213,33 +246,49 @@ namespace sparrow bool any_allocator::equal(const any_allocator& rhs) const { // YOLO!! - return std::visit([&rhs](auto&& arg) - { - using A = std::decay_t; - if constexpr (can_any_allocator_sbo) + return std::visit( + [&rhs](auto&& arg) { - return std::visit([&arg](auto&& arg2) + using A = std::decay_t; + if constexpr (can_any_allocator_sbo) { - using A2 = std::decay_t; - if constexpr (can_any_allocator_sbo && std::same_as) - return arg == arg2; - else - return false; - }, rhs.m_storage); - } - else - { - return std::visit([&arg](auto&& arg2) + return std::visit( + [&arg](auto&& arg2) + { + using A2 = std::decay_t; + if constexpr (can_any_allocator_sbo && std::same_as) + { + return arg == arg2; + } + else + { + return false; + } + }, + rhs.m_storage + ); + } + else { - using A2 = std::decay_t; - if constexpr (can_any_allocator_sbo) - return false; - else - return arg->equal(*arg2); - }, rhs.m_storage); - } - - }, m_storage); + return std::visit( + [&arg](auto&& arg2) + { + using A2 = std::decay_t; + if constexpr (can_any_allocator_sbo) + { + return false; + } + else + { + return arg->equal(*arg2); + } + }, + rhs.m_storage + ); + } + }, + m_storage + ); } template @@ -248,4 +297,3 @@ namespace sparrow return lhs.equal(rhs); } } - diff --git a/include/sparrow/buffer.hpp b/include/sparrow/buffer.hpp index 9563d62c..4335f07d 100644 --- a/include/sparrow/buffer.hpp +++ b/include/sparrow/buffer.hpp @@ -58,7 +58,7 @@ namespace sparrow }; buffer_base() = default; - + template constexpr buffer_base(const A& a) noexcept; @@ -71,7 +71,7 @@ namespace sparrow ~buffer_base(); buffer_base(buffer_base&&) = default; - + template constexpr buffer_base(buffer_base&& rhs, const A& a); @@ -86,7 +86,7 @@ namespace sparrow constexpr void assign_storage(pointer p, size_type n, size_type cap); private: - + allocator_type m_alloc; buffer_data m_data; }; @@ -107,8 +107,11 @@ namespace sparrow using base_type = buffer_base; using alloc_traits = typename base_type::alloc_traits; - static_assert(std::same_as, T>, - "buffer must have a non-const, non-volatile, non-reference value_type"); + static_assert( + std::same_as, T>, + "buffer must have a non-const, non-volatile, non-reference value_type" + ); + public: using allocator_type = typename base_type::allocator_type; @@ -127,8 +130,8 @@ namespace sparrow buffer() = default; template - requires (not std::same_as> and allocator) - constexpr explicit buffer(const A& a) + requires(not std::same_as> and allocator) + constexpr explicit buffer(const A& a) : base_type(a) { } @@ -211,7 +214,7 @@ namespace sparrow constexpr size_type max_size() const noexcept; // Modifiers - + constexpr void clear(); constexpr void reserve(size_type new_cap); constexpr void resize(size_type new_size); @@ -221,20 +224,19 @@ namespace sparrow private: - using base_type::get_data; using base_type::get_allocator; + using base_type::get_data; template constexpr void resize_impl(size_type new_size, F&& initializer); template constexpr void assign_range_impl(It first, It last, std::forward_iterator_tag); - + constexpr void erase_at_end(pointer p); template - constexpr pointer - allocate_and_copy(size_type n, It first, It last); + constexpr pointer allocate_and_copy(size_type n, It first, It last); // The following methods are static because: // - they accept an allocator argument, and do not depend on @@ -244,24 +246,19 @@ namespace sparrow // on copy / move, as these methods will be called with allocators // from different instances of buffers. - static constexpr size_type - check_init_length(size_type n, const allocator_type& a); + static constexpr size_type check_init_length(size_type n, const allocator_type& a); - static constexpr size_type - max_size_impl(const allocator_type& a) noexcept; + static constexpr size_type max_size_impl(const allocator_type& a) noexcept; - static constexpr pointer - default_initialize(pointer begin, size_type n, allocator_type& a); + static constexpr pointer default_initialize(pointer begin, size_type n, allocator_type& a); static constexpr pointer fill_initialize(pointer begin, size_type n, const value_type& v, allocator_type& a); template - static constexpr pointer - copy_initialize(It first, It last, pointer begin, allocator_type& a); + static constexpr pointer copy_initialize(It first, It last, pointer begin, allocator_type& a); - static constexpr void - destroy(pointer first, pointer last, allocator_type& a); + static constexpr void destroy(pointer first, pointer last, allocator_type& a); }; template @@ -297,7 +294,7 @@ namespace sparrow : m_alloc(a) { } - + template template constexpr buffer_base::buffer_base(size_type n, const A& a) @@ -335,7 +332,7 @@ namespace sparrow } template - constexpr auto buffer_base::get_allocator() const noexcept -> const allocator_type& + constexpr auto buffer_base::get_allocator() const noexcept -> const allocator_type& { return m_alloc; } @@ -413,13 +410,7 @@ namespace sparrow constexpr buffer::buffer(std::initializer_list init, const A& a) : base_type(check_init_length(init.size(), a), a) { - get_data().p_end = copy_initialize - ( - init.begin(), - init.end(), - get_data().p_begin, - get_allocator() - ); + get_data().p_end = copy_initialize(init.begin(), init.end(), get_data().p_begin, get_allocator()); } template @@ -427,13 +418,7 @@ namespace sparrow constexpr buffer::buffer(It first, It last, const A& a) : base_type(check_init_length(std::distance(first, last), a), a) { - get_data().p_end = copy_initialize - ( - first, - last, - get_data().p_begin, - get_allocator() - ); + get_data().p_end = copy_initialize(first, last, get_data().p_begin, get_allocator()); } template @@ -446,13 +431,7 @@ namespace sparrow buffer::buffer(const buffer& rhs) : base_type(rhs.size(), rhs.get_allocator()) { - get_data().p_end = copy_initialize - ( - rhs.begin(), - rhs.end(), - get_data().p_begin, - get_allocator() - ); + get_data().p_end = copy_initialize(rhs.begin(), rhs.end(), get_data().p_begin, get_allocator()); } template @@ -460,13 +439,7 @@ namespace sparrow buffer::buffer(const buffer& rhs, const A& a) : base_type(rhs.size(), a) { - get_data().p_end = copy_initialize - ( - rhs.begin(), - rhs.end(), - get_data().p_begin, - get_allocator() - ); + get_data().p_end = copy_initialize(rhs.begin(), rhs.end(), get_data().p_begin, get_allocator()); } template @@ -481,13 +454,7 @@ namespace sparrow else if (!rhs.empty()) { this->create_storage(rhs.size()); - get_data().p_end = copy_initialize - ( - rhs.begin(), - rhs.end(), - get_data().p_begin, - get_allocator() - ); + get_data().p_end = copy_initialize(rhs.begin(), rhs.end(), get_data().p_begin, get_allocator()); rhs.clear(); } } @@ -498,11 +465,7 @@ namespace sparrow if (std::addressof(rhs) != this) { // We assume that any_allocator never propagates on assign - assign_range_impl( - rhs.get_data().p_begin, - rhs.get_data().p_end, - std::random_access_iterator_tag() - ); + assign_range_impl(rhs.get_data().p_begin, rhs.get_data().p_end, std::random_access_iterator_tag()); } return *this; } @@ -628,7 +591,7 @@ namespace sparrow { return reverse_iterator(end()); } - + template constexpr auto buffer::rend() noexcept -> reverse_iterator { @@ -640,7 +603,7 @@ namespace sparrow { return crbegin(); } - + template constexpr auto buffer::rend() const noexcept -> const_reverse_iterator { @@ -664,7 +627,7 @@ namespace sparrow { return get_data().p_begin == get_data().p_end; } - + template constexpr auto buffer::capacity() const noexcept -> size_type { @@ -682,7 +645,7 @@ namespace sparrow { return max_size_impl(get_allocator()); } - + template constexpr void buffer::clear() { @@ -705,27 +668,33 @@ namespace sparrow std::make_move_iterator(get_data().p_end) ); destroy(get_data().p_begin, get_data().p_end, get_allocator()); - this->deallocate(get_data().p_begin, get_data().p_storage_end - get_data().p_begin); + this->deallocate(get_data().p_begin, get_data().p_storage_end - get_data().p_begin); this->assign_storage(tmp, old_size, new_cap); } } - + template constexpr void buffer::resize(size_type new_size) { - resize_impl(new_size, [this](size_type nb_init) - { - get_data().p_end = default_initialize(get_data().p_end, nb_init, get_allocator()); - }); + resize_impl( + new_size, + [this](size_type nb_init) + { + get_data().p_end = default_initialize(get_data().p_end, nb_init, get_allocator()); + } + ); } - + template constexpr void buffer::resize(size_type new_size, const value_type& value) { - resize_impl(new_size, [this, &value](size_type nb_init) - { - get_data().p_end = fill_initialize(get_data().p_end, nb_init, value, get_allocator()); - }); + resize_impl( + new_size, + [this, &value](size_type nb_init) + { + get_data().p_end = fill_initialize(get_data().p_end, nb_init, value, get_allocator()); + } + ); } template @@ -736,7 +705,7 @@ namespace sparrow buffer(std::make_move_iterator(begin()), std::make_move_iterator(end()), get_allocator()).swap(*this); } } - + template constexpr void buffer::swap(buffer& rhs) { @@ -765,7 +734,7 @@ namespace sparrow erase_at_end(get_data().p_begin + new_size); } } - + template template constexpr void buffer::assign_range_impl(It first, It last, std::forward_iterator_tag) @@ -810,7 +779,7 @@ namespace sparrow { copy_initialize(first, last, p, get_allocator()); } - catch(...) + catch (...) { this->deallocate(p, n); throw; @@ -835,7 +804,7 @@ namespace sparrow const size_type alloc_max = std::allocator_traits::max_size(a); return (std::min)(diff_max, alloc_max); } - + template constexpr auto buffer::default_initialize(pointer begin, size_type n, allocator_type& a) -> pointer { @@ -848,7 +817,8 @@ namespace sparrow } template - constexpr auto buffer::fill_initialize(pointer begin, size_type n, const value_type& v, allocator_type& a) -> pointer + constexpr auto + buffer::fill_initialize(pointer begin, size_type n, const value_type& v, allocator_type& a) -> pointer { pointer current = begin; for (; n > 0; --n, ++current) diff --git a/include/sparrow/buffer_view.hpp b/include/sparrow/buffer_view.hpp index cb7c32ab..e98c4e1c 100644 --- a/include/sparrow/buffer_view.hpp +++ b/include/sparrow/buffer_view.hpp @@ -265,4 +265,3 @@ namespace sparrow return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin()); } } - diff --git a/test/test_allocator.cpp b/test/test_allocator.cpp index 9d5ac6bc..acd0cced 100644 --- a/test/test_allocator.cpp +++ b/test/test_allocator.cpp @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "doctest/doctest.h" - #include #include #include @@ -21,6 +19,8 @@ #include "sparrow/allocator.hpp" +#include "doctest/doctest.h" + TEST_SUITE("any_allocator") { TEST_CASE_TEMPLATE_DEFINE("value semantic", A, value_semantic_id) @@ -81,11 +81,13 @@ TEST_SUITE("any_allocator") #if __APPLE__ // /usr/lib/libc++.1.dylib is missing the symbol __ZNSt3__13pmr20get_default_resourceEv, leading // to an exception at runtime. - TEST_CASE_TEMPLATE_INVOKE(value_semantic_id, std::allocator/*, std::pmr::polymorphic_allocator*/); - TEST_CASE_TEMPLATE_INVOKE(allocate_id, std::allocator/*, std::pmr::polymorphic_allocator*/); + TEST_CASE_TEMPLATE_INVOKE( + value_semantic_id, + std::allocator /*, std::pmr::polymorphic_allocator*/ + ); + TEST_CASE_TEMPLATE_INVOKE(allocate_id, std::allocator /*, std::pmr::polymorphic_allocator*/); #else TEST_CASE_TEMPLATE_INVOKE(value_semantic_id, std::allocator, std::pmr::polymorphic_allocator); TEST_CASE_TEMPLATE_INVOKE(allocate_id, std::allocator, std::pmr::polymorphic_allocator); #endif } - diff --git a/test/test_buffer.cpp b/test/test_buffer.cpp index ef53a7a1..f5f220f1 100644 --- a/test/test_buffer.cpp +++ b/test/test_buffer.cpp @@ -73,13 +73,13 @@ namespace sparrow CHECK_EQ(b3[i], expected_value); } - buffer_test_type b4 = { 1u, 3u, 5u }; + buffer_test_type b4 = {1u, 3u, 5u}; CHECK_EQ(b4.size(), 3u); CHECK_EQ(b4[0], 1u); CHECK_EQ(b4[1], 3u); CHECK_EQ(b4[2], 5u); - std::vector exp = { 2u, 4u, 6u }; + std::vector exp = {2u, 4u, 6u}; buffer_test_type b5(exp.cbegin(), exp.cend()); CHECK_EQ(b5.size(), exp.size()); for (size_t i = 0; i < 3u; ++i) From 5eded4b40bd1da5d73678422110032134ffeea0d Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Wed, 17 Apr 2024 10:34:19 +0200 Subject: [PATCH 09/10] Moved methods to the right section --- include/sparrow/buffer.hpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/include/sparrow/buffer.hpp b/include/sparrow/buffer.hpp index 4335f07d..c99c2498 100644 --- a/include/sparrow/buffer.hpp +++ b/include/sparrow/buffer.hpp @@ -212,14 +212,14 @@ namespace sparrow constexpr size_type capacity() const noexcept; constexpr size_type size() const noexcept; constexpr size_type max_size() const noexcept; + constexpr void reserve(size_type new_cap); + constexpr void shrink_to_fit(); // Modifiers constexpr void clear(); - constexpr void reserve(size_type new_cap); constexpr void resize(size_type new_size); constexpr void resize(size_type new_size, const value_type& value); - constexpr void shrink_to_fit(); constexpr void swap(buffer& rhs); private: @@ -646,12 +646,6 @@ namespace sparrow return max_size_impl(get_allocator()); } - template - constexpr void buffer::clear() - { - erase_at_end(get_data().p_begin); - } - template constexpr void buffer::reserve(size_type new_cap) { @@ -673,6 +667,21 @@ namespace sparrow } } + template + constexpr void buffer::shrink_to_fit() + { + if (capacity() != size()) + { + buffer(std::make_move_iterator(begin()), std::make_move_iterator(end()), get_allocator()).swap(*this); + } + } + + template + constexpr void buffer::clear() + { + erase_at_end(get_data().p_begin); + } + template constexpr void buffer::resize(size_type new_size) { @@ -697,15 +706,6 @@ namespace sparrow ); } - template - constexpr void buffer::shrink_to_fit() - { - if (capacity() != size()) - { - buffer(std::make_move_iterator(begin()), std::make_move_iterator(end()), get_allocator()).swap(*this); - } - } - template constexpr void buffer::swap(buffer& rhs) { From da1c6351f6ec0c06a372a8b4cd127bc5a7dac88f Mon Sep 17 00:00:00 2001 From: Johan Mabille Date: Thu, 18 Apr 2024 14:04:30 +0200 Subject: [PATCH 10/10] Relies on autobrief syntax --- include/sparrow/buffer.hpp | 6 ++---- include/sparrow/buffer_view.hpp | 7 +++++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/include/sparrow/buffer.hpp b/include/sparrow/buffer.hpp index c99c2498..d5172b42 100644 --- a/include/sparrow/buffer.hpp +++ b/include/sparrow/buffer.hpp @@ -29,8 +29,7 @@ namespace sparrow { /** - * @class buffer_base - * @brief base class for buffer + * Base class for buffer. * * This class provides memory management for the buffer class. * The constructor and destructor perform allocation and deallocation @@ -92,8 +91,7 @@ namespace sparrow }; /** - * @class buffer - * @brief Object that owns a piece of contiguous memory + * Object that owns a piece of contiguous memory. * * This class provides an API similar to std::vector, with * two main differences: diff --git a/include/sparrow/buffer_view.hpp b/include/sparrow/buffer_view.hpp index e98c4e1c..7c7399ee 100644 --- a/include/sparrow/buffer_view.hpp +++ b/include/sparrow/buffer_view.hpp @@ -19,8 +19,11 @@ namespace sparrow { /* - * @class buffer_view - * @brief Non-owning view of a contiguous sequence of objects of type T. + * Non-owning view of a contiguous sequence of objects of type T. + * + * Although this class looks very similar to std::span, it provides + * methods that are missing in C++20 std::span (like cbegin / cend), + * and additional std::vector-like APIs. */ template class buffer_view