/* * Copyright (C) 2021 The Android Open Source Project * * 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. */ #ifndef TNT_UTILS_FIXEDCAPACITYVECTOR_H #define TNT_UTILS_FIXEDCAPACITYVECTOR_H #include #include #include #include #include #include #include #include #include #include #ifndef NDEBUG #define FILAMENT_FORCE_CAPACITY_CHECK true #else #define FILAMENT_FORCE_CAPACITY_CHECK false #endif namespace utils { /** * FixedCapacityVector is (almost) a drop-in replacement for std::vector<> except it has a * fixed capacity decided at runtime. The vector storage is never reallocated unless reserve() * is called. Operations that add elements to the vector can fail if there is not enough * capacity. * * An empty vector with a given capacity is created with * FixedCapacityVector::with_capacity( capacity ); * * NOTE: When passing an initial size into the FixedCapacityVector constructor, default construction * of the elements is skipped when their construction is trivial. This behavior is different from * std::vector. e.g., std::vector(4) constructs 4 zeros while FixedCapacityVector(4) * allocates 4 uninitialized values. Note that zero initialization is easily achieved by passing in * the optional value argument, e.g. FixedCapacityVector(4, 0) or foo.resize(4, 0). */ template, bool CapacityCheck = true> class UTILS_PUBLIC FixedCapacityVector { public: using allocator_type = A; using value_type = T; using reference = T&; using const_reference = T const&; using size_type = uint32_t; using difference_type = int32_t; using pointer = T*; using const_pointer = T const*; using iterator = pointer; using const_iterator = const_pointer; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; private: using storage_traits = std::allocator_traits; public: /** returns an empty vector with the specified capacity */ static FixedCapacityVector with_capacity( size_type capacity, const allocator_type& allocator = allocator_type()) { return FixedCapacityVector(construct_with_capacity, capacity, allocator); } FixedCapacityVector() = default; explicit FixedCapacityVector(const allocator_type& allocator) noexcept : mCapacityAllocator({}, allocator) { } explicit FixedCapacityVector(size_type size, const allocator_type& allocator = allocator_type()) : mSize(size), mCapacityAllocator(size, allocator) { mData = this->allocator().allocate(this->capacity()); construct(begin(), end()); } FixedCapacityVector(size_type size, const_reference value, const allocator_type& alloc = allocator_type()) : mSize(size), mCapacityAllocator(size, alloc) { mData = this->allocator().allocate(this->capacity()); construct(begin(), end(), value); } FixedCapacityVector(FixedCapacityVector const& rhs) : mSize(rhs.mSize), mCapacityAllocator(rhs.capacity(), storage_traits::select_on_container_copy_construction(rhs.allocator())) { mData = allocator().allocate(capacity()); std::uninitialized_copy(rhs.begin(), rhs.end(), begin()); } FixedCapacityVector(FixedCapacityVector&& rhs) noexcept { this->swap(rhs); } ~FixedCapacityVector() noexcept { destroy(begin(), end()); allocator().deallocate(data(), capacity()); } FixedCapacityVector& operator=(FixedCapacityVector const& rhs) { if (this != &rhs) { FixedCapacityVector t(rhs); this->swap(t); } return *this; } FixedCapacityVector& operator=(FixedCapacityVector&& rhs) noexcept { this->swap(rhs); return *this; } allocator_type get_allocator() const noexcept { return mCapacityAllocator.second(); } // -------------------------------------------------------------------------------------------- iterator begin() noexcept { return data(); } iterator end() noexcept { return data() + size(); } const_iterator begin() const noexcept { return data(); } const_iterator end() const noexcept { return data() + size(); } reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } reverse_iterator rend() noexcept { return reverse_iterator(begin()); } const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } const_iterator cbegin() const noexcept { return begin(); } const_iterator cend() const noexcept { return end(); } const_reverse_iterator crbegin() const noexcept { return rbegin(); } const_reverse_iterator crend() const noexcept { return rend(); } // -------------------------------------------------------------------------------------------- size_type size() const noexcept { return mSize; } size_type capacity() const noexcept { return mCapacityAllocator.first(); } bool empty() const noexcept { return size() == 0; } size_type max_size() const noexcept { return std::min(storage_traits::max_size(allocator()), std::numeric_limits::max()); } // -------------------------------------------------------------------------------------------- reference operator[](size_type n) noexcept { assert(n < size()); return *(begin() + n); } const_reference operator[](size_type n) const noexcept { assert(n < size()); return *(begin() + n); } reference front() noexcept { return *begin(); } const_reference front() const noexcept { return *begin(); } reference back() noexcept { return *(end() - 1); } const_reference back() const noexcept { return *(end() - 1); } value_type* data() noexcept { return mData; } const value_type* data() const noexcept { return mData; } // -------------------------------------------------------------------------------------------- void push_back(const_reference v) { auto pos = assertCapacityForSize(size() + 1); ++mSize; storage_traits::construct(allocator(), pos, v); } void push_back(value_type&& v) { auto pos = assertCapacityForSize(size() + 1); ++mSize; storage_traits::construct(allocator(), pos, std::move(v)); } template reference emplace_back(ARGS&& ... args) { auto pos = assertCapacityForSize(size() + 1); ++mSize; storage_traits::construct(allocator(), pos, std::forward(args)...); return *pos; } void pop_back() { assert(!empty()); --mSize; destroy(end(), end() + 1); } iterator insert(const_iterator position, const_reference v) { if (position == end()) { push_back(v); } else { assertCapacityForSize(size() + 1); pointer p = const_cast(position); move_range(p, end(), p + 1); ++mSize; // here we handle inserting an element of this vector! const_pointer pv = std::addressof(v); if (p <= pv && pv < end()) { *p = *(pv + 1); } else { *p = v; } } return const_cast(position); } iterator insert(const_iterator position, value_type&& v) { if (position == end()) { push_back(std::move(v)); } else { assertCapacityForSize(size() + 1); pointer p = const_cast(position); move_range(p, end(), p + 1); ++mSize; *p = std::move(v); } return const_cast(position); } iterator erase(const_iterator pos) { assert(pos != end()); return erase(pos, pos + 1); } iterator erase(const_iterator first, const_iterator last) { assert(first <= last); auto e = std::move(const_cast(last), end(), const_cast(first)); destroy(e, end()); mSize -= std::distance(first, last); return const_cast(first); } void clear() noexcept { destroy(begin(), end()); mSize = 0; } void resize(size_type count) { assertCapacityForSize(count); if constexpr(std::is_trivially_constructible_v && std::is_trivially_destructible_v) { // we check for triviality here so that the implementation could be non-inline mSize = count; } else { resize_non_trivial(count); } } void resize(size_type count, const_reference v) { assertCapacityForSize(count); resize_non_trivial(count, v); } void swap(FixedCapacityVector& other) { using std::swap; swap(mData, other.mData); swap(mSize, other.mSize); mCapacityAllocator.swap(other.mCapacityAllocator); } UTILS_NOINLINE void reserve(size_type c) { if (c > capacity()) { FixedCapacityVector t(construct_with_capacity, c, allocator()); t.mSize = size(); std::uninitialized_move(begin(), end(), t.begin()); this->swap(t); } } private: enum construct_with_capacity_tag{ construct_with_capacity }; FixedCapacityVector(construct_with_capacity_tag, size_type capacity, const allocator_type& allocator = allocator_type()) : mCapacityAllocator(capacity, allocator) { mData = this->allocator().allocate(this->capacity()); } allocator_type& allocator() noexcept { return mCapacityAllocator.second(); } allocator_type const& allocator() const noexcept { return mCapacityAllocator.second(); } iterator assertCapacityForSize(size_type s) { if constexpr(CapacityCheck || FILAMENT_FORCE_CAPACITY_CHECK) { ASSERT_PRECONDITION(capacity() >= s, "capacity exceeded: requested size %lu, available capacity %lu.", (unsigned long)s, (unsigned long)capacity()); } return end(); } inline void construct(iterator first, iterator last) noexcept { // we check for triviality here so that the implementation could be non-inline if constexpr(!std::is_trivially_constructible_v) { construct_non_trivial(first, last); } } void construct(iterator first, iterator last, const_reference proto) noexcept { #pragma nounroll while (first != last) { storage_traits::construct(allocator(), first++, proto); } } // should this be NOINLINE? void construct_non_trivial(iterator first, iterator last) noexcept { #pragma nounroll while (first != last) { storage_traits::construct(allocator(), first++); } } inline void destroy(iterator first, iterator last) noexcept { // we check for triviality here so that the implementation could be non-inline if constexpr(!std::is_trivially_destructible_v) { destroy_non_trivial(first, last); } } // should this be NOINLINE? void destroy_non_trivial(iterator first, iterator last) noexcept { #pragma nounroll while (first != last) { storage_traits::destroy(allocator(), --last); } } // should this be NOINLINE? void resize_non_trivial(size_type count) { if (count > size()) { construct(end(), begin() + count); } else if (count < size()) { destroy(begin() + count, end()); } mSize = count; } // should this be NOINLINE? void resize_non_trivial(size_type count, const_reference v) { if (count > size()) { construct(end(), begin() + count, v); } else if (count < size()) { destroy(begin() + count, end()); } mSize = count; } // should this be NOINLINE? void move_range(pointer s, pointer e, pointer to) { if constexpr(std::is_trivially_copy_assignable_v && std::is_trivially_destructible_v) { // this generates memmove -- which doesn't happen otherwise std::move_backward(s, e, to + std::distance(s, e)); } else { pointer our_end = end(); difference_type n = our_end - to; // nb of elements to move by operator= pointer i = s + n; // 1st element to move by move ctor for (pointer d = our_end ; i < our_end ; ++i, ++d) { storage_traits::construct(allocator(), d, std::move(*i)); } std::move_backward(s, s + n, our_end); } } template class SizeTypeWrapper { TYPE value{}; public: SizeTypeWrapper() noexcept = default; SizeTypeWrapper(SizeTypeWrapper const& rhs) noexcept = default; explicit SizeTypeWrapper(TYPE value) noexcept : value(value) { } SizeTypeWrapper operator=(TYPE rhs) noexcept { value = rhs; return *this; } operator TYPE() const noexcept { return value; } }; pointer mData{}; size_type mSize{}; compressed_pair, allocator_type> mCapacityAllocator{}; }; } // namespace utils #endif // TNT_UTILS_FIXEDCAPACITYVECTOR_H