code/include/swoc/Vectray.h (268 lines of code) (raw):

// SPDX-License-Identifier: Apache-2.0 // Copyright Apache Software Foundation 2019 /** @file Container that acts like a vector but has static storage to avoid memory allocation for some specified number of elements. */ #pragma once #include <array> #include <vector> #include <variant> #include <new> #include <cstddef> #include <swoc/MemSpan.h> #include <swoc/swoc_meta.h> namespace swoc { inline namespace SWOC_VERSION_NS { /** Vectray provides a combination of static and dynamic storage modeled as an array. * * @tparam T Type of elements in the array. * @tparam N Number of statically allocated elements. * @tparam A Allocator. * * The goal is to provide static storage for the common case, avoiding memory allocation, while * still handling exceptional cases that need more storage. A common case is for @a N == 1 where * there is almost always a single value, but it is possible to have multiple values. @c Vectray * makes the single value case require no allocation while transparently handling the multiple * value case. * * The interface is designed to mimic that of @c std::vector. */ template <typename T, size_t N, class A = std::allocator<T>> class Vectray { using self_type = Vectray; ///< Self reference type. using vector_type = std::vector<T, A>; ///< Internal dynamic storage type. public: // STL compliance types. using value_type = T; ///< Element type for container. using reference = std::remove_reference<T> &; ///< Reference to element. using const_reference = std::remove_reference<T> const &; ///< Reference to constant element. using pointer = std::remove_reference<T> *; ///< Pointer to element. using const_pointer = std::remove_reference<T> const *; ///< Pointer to constant element. using allocator_type = A; ///< Dynamic storage allocator. using size_type = typename vector_type::size_type; ///< Type for element count. using difference_type = typename vector_type::difference_type; ///< Iterator difference. using iterator = typename swoc::MemSpan<T>::iterator; ///< Element iteration. using const_iterator = typename swoc::MemSpan<const T>::iterator; ///< Constant element iteration. // Need to add reverse iterators - @c reverse_iterator and @c const_reverse_iterator /// Internal (fixed) storage. struct FixedStore { std::array<std::byte, sizeof(T) * N> _raw; ///< Raw memory for element storage. size_t _count = 0; ///< Number of valid elements. allocator_type _a; ///< Allocator instance - used for construction. FixedStore() = default; ///< Default construct - empty. /** Construct with specific allocator @a a. * * @param a Allocator. */ explicit FixedStore(allocator_type const &a) : _a(a) {} ~FixedStore(); /// @return A span containing the valid elements. MemSpan<T> span(); /// @return A pointer to the data. T *data(); /// @return A pointer to the data. T const *data() const; }; using DynamicStore = vector_type; ///< Dynamic (heap) storage. /// Element access. using span = swoc::MemSpan<T>; /// Constant element access. using const_span = swoc::MemSpan<T const>; public: /// Default constructor, construct an empty container. Vectray(); /// Destructor - destructs all contained elements. /// @internal The internal store takes care of the details. ~Vectray() = default; /// Construct empty instance with allocator. constexpr explicit Vectray(allocator_type const &a) : _store(std::in_place_type_t<FixedStore>{}, a) {} /** Construct with @a n default constructed elements. * * @param n Number of elements. * @param alloc Allocator (optional - default constructed if not a parameter). */ explicit Vectray(size_type n, allocator_type const &alloc = allocator_type{}); /// Move constructor for difference static sized instance. template <size_t M> Vectray(Vectray<T, M, A> &&that); /// Move constructor. Vectray(self_type &&that, allocator_type const &a); /// @return The number of elements in the container. size_type size() const; /// @return A pointer to the data. T *data(); /// @return A pointer to the data. T const *data() const; /// @return @c true if no valid elements, @c false if at least one valid element. bool empty() const; /// Implicitly convert to a @c MemSpan. operator span() { return this->items(); } /// Implicitly convert to a @c MemSpan. operator const_span() const { return this->items(); } /** Index operator. * * @param idx Index of element. * @return A reference to the element. */ T &operator[](size_type idx); /** Index operator (const). * * @param idx Index of element. * @return A @c const reference to the element. */ T const &operator[](size_type idx) const; /// @return A reference to the first element. T const & front() const { return (*this)[0]; } /// @return A reference to the first element. T & front() { return (*this)[0]; } /// @return A reference to the last element. T const & back() const { return (*this)[this->size() - 1]; } /// @return A reference to the last element. T & back() { return (*this)[this->size() - 1]; } /** Append an element by copy. * * @param t Element to add. * @return @a this. */ self_type &push_back(T const &t); /** Append an element by move. * * @param t Element to add. * @return @a this. */ self_type &push_back(T &&t); /** Append an element by direct construction. * * @tparam Args Constructor parameter types. * @param args Constructor arguments. * @return @a this */ template <typename... Args> self_type &emplace_back(Args &&...args); /** Remove an element from the end of the current elements. * * @return @a this. */ self_type &pop_back(); /// Iterator for first element. const_iterator begin() const; /// Iterator past last element. const_iterator end() const; /// Iterator for last element. iterator begin(); /// Iterator past last element. iterator end(); /// Force at internal storage to hold at least @a n items. void reserve(size_type n); protected: /// Content storage. /// @note This is constructed as fixed but can change to dynamic. It can never change back. std::variant<FixedStore, DynamicStore> _store; static constexpr auto FIXED = 0; ///< Variant index for fixed storage. static constexpr auto DYNAMIC = 1; ///< Variant index for dynamic storage. /// Get the span of the valid items. span items(); /// Get the span of the valid items. const_span items() const; /// Default size to reserve in the vector when switching to dynamic. static constexpr size_type BASE_DYNAMIC_SIZE = (7 * N) / 5; /** Transfer from fixed storage to dynamic storage. * * @param rN Numer of elements of storage to reserve in the vector. * * @note Must be called at most once for any instance. */ void transfer(size_type rN = BASE_DYNAMIC_SIZE); }; // --- Implementation --- template <typename T, size_t N, typename A> Vectray<T, N, A>::Vectray() {} template <typename T, size_t N, class A> Vectray<T, N, A>::Vectray(Vectray::size_type n, allocator_type const &) : Vectray() { this->reserve(n); while (n-- > 0) { this->emplace_back(); } } template <typename T, size_t N, class A> template <size_t M> Vectray<T, N, A>::Vectray(Vectray<T, M, A> &&that) { // If @a that is already a vector, always move that here. if (DYNAMIC == that._store.index()) { _store = std::move(std::get<DYNAMIC>(that._store)); } else { auto span = std::get<FIXED>(that._store).span(); if (span.size() > N) { } else { for (auto &&item : span) { this->template emplace_back<T, N, A>(std::move(item)); } } } } template <typename T, size_t N, class A> Vectray<T, N, A>::FixedStore::~FixedStore() { for (auto &item : this->span()) { std::destroy_at(std::addressof(item)); } } template <typename T, size_t N, class A> MemSpan<T> Vectray<T, N, A>::FixedStore::span() { return MemSpan<std::byte>(_raw).template rebind<T>(); } template <typename T, size_t N, class A> T * Vectray<T, N, A>::FixedStore::data() { return reinterpret_cast<T *>(_raw.data()); } template <typename T, size_t N, class A> T const * Vectray<T, N, A>::FixedStore::data() const { return reinterpret_cast<T *>(_raw.data()); } template <typename T, size_t N, typename A> T & Vectray<T, N, A>::operator[](size_type idx) { return this->items()[idx]; } template <typename T, size_t N, typename A> T const & Vectray<T, N, A>::operator[](size_type idx) const { return this->items()[idx]; } template <typename T, size_t N, typename A> auto Vectray<T, N, A>::push_back(const T &t) -> self_type & { std::visit(swoc::meta::vary{[&](FixedStore &fs) -> void { if (fs._count < N) { new (reinterpret_cast<T *>(fs._raw.data()) + fs._count++) T(t); // A::traits ? } else { this->transfer(); std::get<DYNAMIC>(_store).push_back(t); } }, [&](DynamicStore &ds) -> void { ds.push_back(t); }}, _store); return *this; } template <typename T, size_t N, typename A> auto Vectray<T, N, A>::push_back(T &&t) -> self_type & { std::visit(swoc::meta::vary{[&](FixedStore &fs) -> void { if (fs._count < N) { new (reinterpret_cast<T *>(fs._raw.data()) + fs._count++) T(std::move(t)); // A::traits ? } else { this->transfer(); std::get<DYNAMIC>(_store).push_back(std::move(t)); } }, [&](DynamicStore &ds) -> void { ds.push_back(std::move(t)); }}, _store); return *this; } template <typename T, size_t N, class A> template <typename... Args> typename Vectray<T, N, A>::self_type & Vectray<T, N, A>::emplace_back(Args &&...args) { if (_store.index() == FIXED) { auto &fs{std::get<FIXED>(_store)}; if (fs._count < N) { new (reinterpret_cast<T *>(fs._raw.data()) + fs._count++) T(std::forward<Args>(args)...); // A::traits ? return *this; } this->transfer(); // transfer to dynamic and fall through to add item. } std::get<DYNAMIC>(_store).emplace_back(std::forward<Args>(args)...); return *this; } template <typename T, size_t N, class A> auto Vectray<T, N, A>::pop_back() -> self_type & { std::visit(swoc::meta::vary{[&](FixedStore &fs) -> void { std::destroy_at(fs.span()[--fs._count]); }, [&](DynamicStore &ds) -> void { ds.pop_back(); }}, _store); return *this; } template <typename T, size_t N, typename A> auto Vectray<T, N, A>::size() const -> size_type { return std::visit( swoc::meta::vary{[](FixedStore const &fs) { return fs._count; }, [](DynamicStore const &ds) { return ds.size(); }}, _store); } template <typename T, size_t N, typename A> bool Vectray<T, N, A>::empty() const { return std::visit( swoc::meta::vary{[](FixedStore const &fs) { return fs._count == 0; }, [](DynamicStore const &ds) { return ds.empty(); }}, _store); } // --- iterators template <typename T, size_t N, typename A> auto Vectray<T, N, A>::begin() const -> const_iterator { return this->items().begin(); } template <typename T, size_t N, typename A> auto Vectray<T, N, A>::end() const -> const_iterator { return this->items().end(); } template <typename T, size_t N, typename A> auto Vectray<T, N, A>::begin() -> iterator { return this->items().begin(); } template <typename T, size_t N, typename A> auto Vectray<T, N, A>::end() -> iterator { return this->items().end(); } // --- iterators template <typename T, size_t N, class A> void Vectray<T, N, A>::transfer(size_type rN) { DynamicStore tmp{std::get<FIXED>(_store)._a}; tmp.reserve(rN); for (auto &&item : this->items()) { tmp.emplace_back(std::move(item)); // move if supported, copy if not. } // Fixed elements destroyed here, by variant. _store = std::move(tmp); } template <typename T, size_t N, class A> auto Vectray<T, N, A>::items() const -> const_span { return std::visit(swoc::meta::vary{[](FixedStore const &fs) { fs.span(); }, [](DynamicStore const &ds) { return const_span(ds.data(), ds.size()); }}, _store); } template <typename T, size_t N, class A> T * Vectray<T, N, A>::data() { return std::visit(swoc::meta::vary{[](FixedStore const &fs) { fs.data(); }, [](DynamicStore const &ds) { return ds.data(); }}, _store); } template <typename T, size_t N, class A> T const * Vectray<T, N, A>::data() const { return std::visit(swoc::meta::vary{[](FixedStore const &fs) { fs.data(); }, [](DynamicStore const &ds) { return ds.data(); }}, _store); } template <typename T, size_t N, class A> auto Vectray<T, N, A>::items() -> span { return std::visit( swoc::meta::vary{[](FixedStore &fs) { return fs.span(); }, [](DynamicStore &ds) { return span(ds.data(), ds.size()); }}, _store); } template <typename T, size_t N, class A> void Vectray<T, N, A>::reserve(Vectray::size_type n) { if (DYNAMIC == _store.index()) { std::get<DYNAMIC>(_store).reserve(n); } else if (n > N) { this->transfer(n); } } }} // namespace swoc::SWOC_VERSION_NS