// Copyright 2017 The Abseil Authors. // // 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. // // ----------------------------------------------------------------------------- // File: inlined_vector.h // ----------------------------------------------------------------------------- // // This header file contains the declaration and definition of an "inlined // vector" which behaves in an equivalent fashion to a `std::vector`, except // that storage for small sequences of the vector are provided inline without // requiring any heap allocation. // An `absl::InlinedVector` specifies the size N at which to inline as one // of its template parameters. Vectors of length <= N are provided inline. // Typically N is very small (e.g., 4) so that sequences that are expected to be // short do not require allocations. // An `absl::InlinedVector` does not usually require a specific allocator; if // the inlined vector grows beyond its initial constraints, it will need to // allocate (as any normal `std::vector` would) and it will generally use the // default allocator in that case; optionally, a custom allocator may be // specified using an `absl::InlinedVector` construction. #ifndef ABSL_CONTAINER_INLINED_VECTOR_H_ #define ABSL_CONTAINER_INLINED_VECTOR_H_ #include #include #include #include #include #include #include #include #include #include #include "absl/algorithm/algorithm.h" #include "absl/base/internal/throw_delegate.h" #include "absl/base/optimization.h" #include "absl/base/port.h" #include "absl/memory/memory.h" namespace absl { inline namespace lts_2018_06_20 { // ----------------------------------------------------------------------------- // InlinedVector // ----------------------------------------------------------------------------- // // An `absl::InlinedVector` is designed to be a drop-in replacement for // `std::vector` for use cases where the vector's size is sufficiently small // that it can be inlined. If the inlined vector does grow beyond its estimated // size, it will trigger an initial allocation on the heap, and will behave as a // `std:vector`. The API of the `absl::InlinedVector` within this file is // designed to cover the same API footprint as covered by `std::vector`. template > class InlinedVector { using AllocatorTraits = std::allocator_traits; public: using allocator_type = A; using value_type = typename allocator_type::value_type; using pointer = typename allocator_type::pointer; using const_pointer = typename allocator_type::const_pointer; using reference = typename allocator_type::reference; using const_reference = typename allocator_type::const_reference; using size_type = typename allocator_type::size_type; using difference_type = typename allocator_type::difference_type; using iterator = pointer; using const_iterator = const_pointer; using reverse_iterator = std::reverse_iterator; using const_reverse_iterator = std::reverse_iterator; InlinedVector() noexcept(noexcept(allocator_type())) : allocator_and_tag_(allocator_type()) {} explicit InlinedVector(const allocator_type& alloc) noexcept : allocator_and_tag_(alloc) {} // Create a vector with n copies of value_type(). explicit InlinedVector(size_type n) : allocator_and_tag_(allocator_type()) { InitAssign(n); } // Create a vector with n copies of elem InlinedVector(size_type n, const value_type& elem, const allocator_type& alloc = allocator_type()) : allocator_and_tag_(alloc) { InitAssign(n, elem); } // Create and initialize with the elements [first .. last). // The unused enable_if argument restricts this constructor so that it is // elided when value_type is an integral type. This prevents ambiguous // interpretation between a call to this constructor with two integral // arguments and a call to the preceding (n, elem) constructor. template InlinedVector( InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type(), typename std::enable_if::value>::type* = nullptr) : allocator_and_tag_(alloc) { AppendRange(first, last); } InlinedVector(std::initializer_list init, const allocator_type& alloc = allocator_type()) : allocator_and_tag_(alloc) { AppendRange(init.begin(), init.end()); } InlinedVector(const InlinedVector& v); InlinedVector(const InlinedVector& v, const allocator_type& alloc); // This move constructor does not allocate and only moves the underlying // objects, so its `noexcept` specification depends on whether moving the // underlying objects can throw or not. We assume // a) move constructors should only throw due to allocation failure and // b) if `value_type`'s move constructor allocates, it uses the same // allocation function as the `InlinedVector`'s allocator, so the move // constructor is non-throwing if the allocator is non-throwing or // `value_type`'s move constructor is specified as `noexcept`. InlinedVector(InlinedVector&& v) noexcept( absl::allocator_is_nothrow::value || std::is_nothrow_move_constructible::value); // This move constructor allocates and also moves the underlying objects, so // its `noexcept` specification depends on whether the allocation can throw // and whether moving the underlying objects can throw. Based on the same // assumptions above, the `noexcept` specification is dominated by whether the // allocation can throw regardless of whether `value_type`'s move constructor // is specified as `noexcept`. InlinedVector(InlinedVector&& v, const allocator_type& alloc) noexcept( absl::allocator_is_nothrow::value); ~InlinedVector() { clear(); } InlinedVector& operator=(const InlinedVector& v) { if (this == &v) { return *this; } // Optimized to avoid reallocation. // Prefer reassignment to copy construction for elements. if (size() < v.size()) { // grow reserve(v.size()); std::copy(v.begin(), v.begin() + size(), begin()); std::copy(v.begin() + size(), v.end(), std::back_inserter(*this)); } else { // maybe shrink erase(begin() + v.size(), end()); std::copy(v.begin(), v.end(), begin()); } return *this; } InlinedVector& operator=(InlinedVector&& v) { if (this == &v) { return *this; } if (v.allocated()) { clear(); tag().set_allocated_size(v.size()); init_allocation(v.allocation()); v.tag() = Tag(); } else { if (allocated()) clear(); // Both are inlined now. if (size() < v.size()) { auto mid = std::make_move_iterator(v.begin() + size()); std::copy(std::make_move_iterator(v.begin()), mid, begin()); UninitializedCopy(mid, std::make_move_iterator(v.end()), end()); } else { auto new_end = std::copy(std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()), begin()); Destroy(new_end, end()); } tag().set_inline_size(v.size()); } return *this; } InlinedVector& operator=(std::initializer_list init) { AssignRange(init.begin(), init.end()); return *this; } // InlinedVector::assign() // // Replaces the contents of the inlined vector with copies of those in the // iterator range [first, last). template void assign( InputIterator first, InputIterator last, typename std::enable_if::value>::type* = nullptr) { AssignRange(first, last); } // Overload of `InlinedVector::assign()` to take values from elements of an // initializer list void assign(std::initializer_list init) { AssignRange(init.begin(), init.end()); } // Overload of `InlinedVector::assign()` to replace the first `n` elements of // the inlined vector with `elem` values. void assign(size_type n, const value_type& elem) { if (n <= size()) { // Possibly shrink std::fill_n(begin(), n, elem); erase(begin() + n, end()); return; } // Grow reserve(n); std::fill_n(begin(), size(), elem); if (allocated()) { UninitializedFill(allocated_space() + size(), allocated_space() + n, elem); tag().set_allocated_size(n); } else { UninitializedFill(inlined_space() + size(), inlined_space() + n, elem); tag().set_inline_size(n); } } // InlinedVector::size() // // Returns the number of elements in the inlined vector. size_type size() const noexcept { return tag().size(); } // InlinedVector::empty() // // Checks if the inlined vector has no elements. bool empty() const noexcept { return (size() == 0); } // InlinedVector::capacity() // // Returns the number of elements that can be stored in an inlined vector // without requiring a reallocation of underlying memory. Note that for // most inlined vectors, `capacity()` should equal its initial size `N`; for // inlined vectors which exceed this capacity, they will no longer be inlined, // and `capacity()` will equal its capacity on the allocated heap. size_type capacity() const noexcept { return allocated() ? allocation().capacity() : N; } // InlinedVector::max_size() // // Returns the maximum number of elements the vector can hold. size_type max_size() const noexcept { // One bit of the size storage is used to indicate whether the inlined // vector is allocated; as a result, the maximum size of the container that // we can express is half of the max for our size type. return std::numeric_limits::max() / 2; } // InlinedVector::data() // // Returns a const T* pointer to elements of the inlined vector. This pointer // can be used to access (but not modify) the contained elements. // Only results within the range `[0,size())` are defined. const_pointer data() const noexcept { return allocated() ? allocated_space() : inlined_space(); } // Overload of InlinedVector::data() to return a T* pointer to elements of the // inlined vector. This pointer can be used to access and modify the contained // elements. pointer data() noexcept { return allocated() ? allocated_space() : inlined_space(); } // InlinedVector::clear() // // Removes all elements from the inlined vector. void clear() noexcept { size_type s = size(); if (allocated()) { Destroy(allocated_space(), allocated_space() + s); allocation().Dealloc(allocator()); } else if (s != 0) { // do nothing for empty vectors Destroy(inlined_space(), inlined_space() + s); } tag() = Tag(); } // InlinedVector::at() // // Returns the ith element of an inlined vector. const value_type& at(size_type i) const { if (ABSL_PREDICT_FALSE(i >= size())) { base_internal::ThrowStdOutOfRange( "InlinedVector::at failed bounds check"); } return data()[i]; } // InlinedVector::operator[] // // Returns the ith element of an inlined vector using the array operator. const value_type& operator[](size_type i) const { assert(i < size()); return data()[i]; } // Overload of InlinedVector::at() to return the ith element of an inlined // vector. value_type& at(size_type i) { if (i >= size()) { base_internal::ThrowStdOutOfRange( "InlinedVector::at failed bounds check"); } return data()[i]; } // Overload of InlinedVector::operator[] to return the ith element of an // inlined vector. value_type& operator[](size_type i) { assert(i < size()); return data()[i]; } // InlinedVector::back() // // Returns a reference to the last element of an inlined vector. value_type& back() { assert(!empty()); return at(size() - 1); } // Overload of InlinedVector::back() returns a reference to the last element // of an inlined vector of const values. const value_type& back() const { assert(!empty()); return at(size() - 1); } // InlinedVector::front() // // Returns a reference to the first element of an inlined vector. value_type& front() { assert(!empty()); return at(0); } // Overload of InlinedVector::front() returns a reference to the first element // of an inlined vector of const values. const value_type& front() const { assert(!empty()); return at(0); } // InlinedVector::emplace_back() // // Constructs and appends an object to the inlined vector. // // Returns a reference to the inserted element. template value_type& emplace_back(Args&&... args) { size_type s = size(); assert(s <= capacity()); if (ABSL_PREDICT_FALSE(s == capacity())) { return GrowAndEmplaceBack(std::forward(args)...); } assert(s < capacity()); value_type* space; if (allocated()) { tag().set_allocated_size(s + 1); space = allocated_space(); } else { tag().set_inline_size(s + 1); space = inlined_space(); } return Construct(space + s, std::forward(args)...); } // InlinedVector::push_back() // // Appends a const element to the inlined vector. void push_back(const value_type& t) { emplace_back(t); } // Overload of InlinedVector::push_back() to append a move-only element to the // inlined vector. void push_back(value_type&& t) { emplace_back(std::move(t)); } // InlinedVector::pop_back() // // Removes the last element (which is destroyed) in the inlined vector. void pop_back() { assert(!empty()); size_type s = size(); if (allocated()) { Destroy(allocated_space() + s - 1, allocated_space() + s); tag().set_allocated_size(s - 1); } else { Destroy(inlined_space() + s - 1, inlined_space() + s); tag().set_inline_size(s - 1); } } // InlinedVector::resize() // // Resizes the inlined vector to contain `n` elements. If `n` is smaller than // the inlined vector's current size, extra elements are destroyed. If `n` is // larger than the initial size, new elements are value-initialized. void resize(size_type n); // Overload of InlinedVector::resize() to resize the inlined vector to contain // `n` elements. If `n` is larger than the current size, enough copies of // `elem` are appended to increase its size to `n`. void resize(size_type n, const value_type& elem); // InlinedVector::begin() // // Returns an iterator to the beginning of the inlined vector. iterator begin() noexcept { return data(); } // Overload of InlinedVector::begin() for returning a const iterator to the // beginning of the inlined vector. const_iterator begin() const noexcept { return data(); } // InlinedVector::cbegin() // // Returns a const iterator to the beginning of the inlined vector. const_iterator cbegin() const noexcept { return begin(); } // InlinedVector::end() // // Returns an iterator to the end of the inlined vector. iterator end() noexcept { return data() + size(); } // Overload of InlinedVector::end() for returning a const iterator to the end // of the inlined vector. const_iterator end() const noexcept { return data() + size(); } // InlinedVector::cend() // // Returns a const iterator to the end of the inlined vector. const_iterator cend() const noexcept { return end(); } // InlinedVector::rbegin() // // Returns a reverse iterator from the end of the inlined vector. reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } // Overload of InlinedVector::rbegin() for returning a const reverse iterator // from the end of the inlined vector. const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } // InlinedVector::crbegin() // // Returns a const reverse iterator from the end of the inlined vector. const_reverse_iterator crbegin() const noexcept { return rbegin(); } // InlinedVector::rend() // // Returns a reverse iterator from the beginning of the inlined vector. reverse_iterator rend() noexcept { return reverse_iterator(begin()); } // Overload of InlinedVector::rend() for returning a const reverse iterator // from the beginning of the inlined vector. const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } // InlinedVector::crend() // // Returns a reverse iterator from the beginning of the inlined vector. const_reverse_iterator crend() const noexcept { return rend(); } // InlinedVector::emplace() // // Constructs and inserts an object to the inlined vector at the given // `position`, returning an iterator pointing to the newly emplaced element. template iterator emplace(const_iterator position, Args&&... args); // InlinedVector::insert() // // Inserts an element of the specified value at `position`, returning an // iterator pointing to the newly inserted element. iterator insert(const_iterator position, const value_type& v) { return emplace(position, v); } // Overload of InlinedVector::insert() for inserting an element of the // specified rvalue, returning an iterator pointing to the newly inserted // element. iterator insert(const_iterator position, value_type&& v) { return emplace(position, std::move(v)); } // Overload of InlinedVector::insert() for inserting `n` elements of the // specified value at `position`, returning an iterator pointing to the first // of the newly inserted elements. iterator insert(const_iterator position, size_type n, const value_type& v) { return InsertWithCount(position, n, v); } // Overload of `InlinedVector::insert()` to disambiguate the two // three-argument overloads of `insert()`, returning an iterator pointing to // the first of the newly inserted elements. template ::iterator_category, std::input_iterator_tag>::value>::type> iterator insert(const_iterator position, InputIterator first, InputIterator last) { using IterType = typename std::iterator_traits::iterator_category; return InsertWithRange(position, first, last, IterType()); } // Overload of InlinedVector::insert() for inserting a list of elements at // `position`, returning an iterator pointing to the first of the newly // inserted elements. iterator insert(const_iterator position, std::initializer_list init) { return insert(position, init.begin(), init.end()); } // InlinedVector::erase() // // Erases the element at `position` of the inlined vector, returning an // iterator pointing to the following element or the container's end if the // last element was erased. iterator erase(const_iterator position) { assert(position >= begin()); assert(position < end()); iterator pos = const_cast(position); std::move(pos + 1, end(), pos); pop_back(); return pos; } // Overload of InlinedVector::erase() for erasing all elements in the // iterator range [first, last) in the inlined vector, returning an iterator // pointing to the first element following the range erased, or the // container's end if range included the container's last element. iterator erase(const_iterator first, const_iterator last); // InlinedVector::reserve() // // Enlarges the underlying representation of the inlined vector so it can hold // at least `n` elements. This method does not change `size()` or the actual // contents of the vector. // // Note that if `n` does not exceed the inlined vector's initial size `N`, // `reserve()` will have no effect; if it does exceed its initial size, // `reserve()` will trigger an initial allocation and move the inlined vector // onto the heap. If the vector already exists on the heap and the requested // size exceeds it, a reallocation will be performed. void reserve(size_type n) { if (n > capacity()) { // Make room for new elements EnlargeBy(n - size()); } } // InlinedVector::shrink_to_fit() // // Reduces memory usage by freeing unused memory. // After this call `capacity()` will be equal to `max(N, size())`. // // If `size() <= N` and the elements are currently stored on the heap, they // will be moved to the inlined storage and the heap memory deallocated. // If `size() > N` and `size() < capacity()` the elements will be moved to // a reallocated storage on heap. void shrink_to_fit() { const auto s = size(); if (!allocated() || s == capacity()) { // There's nothing to deallocate. return; } if (s <= N) { // Move the elements to the inlined storage. // We have to do this using a temporary, because inlined_storage and // allocation_storage are in a union field. auto temp = std::move(*this); assign(std::make_move_iterator(temp.begin()), std::make_move_iterator(temp.end())); return; } // Reallocate storage and move elements. // We can't simply use the same approach as above, because assign() would // call into reserve() internally and reserve larger capacity than we need. Allocation new_allocation(allocator(), s); UninitializedCopy(std::make_move_iterator(allocated_space()), std::make_move_iterator(allocated_space() + s), new_allocation.buffer()); ResetAllocation(new_allocation, s); } // InlinedVector::swap() // // Swaps the contents of this inlined vector with the contents of `other`. void swap(InlinedVector& other); // InlinedVector::get_allocator() // // Returns the allocator of this inlined vector. allocator_type get_allocator() const { return allocator(); } private: static_assert(N > 0, "inlined vector with nonpositive size"); // It holds whether the vector is allocated or not in the lowest bit. // The size is held in the high bits: // size_ = (size << 1) | is_allocated; class Tag { public: Tag() : size_(0) {} size_type size() const { return size_ >> 1; } void add_size(size_type n) { size_ += n << 1; } void set_inline_size(size_type n) { size_ = n << 1; } void set_allocated_size(size_type n) { size_ = (n << 1) | 1; } bool allocated() const { return size_ & 1; } private: size_type size_; }; // Derives from allocator_type to use the empty base class optimization. // If the allocator_type is stateless, we can 'store' // our instance of it for free. class AllocatorAndTag : private allocator_type { public: explicit AllocatorAndTag(const allocator_type& a, Tag t = Tag()) : allocator_type(a), tag_(t) { } Tag& tag() { return tag_; } const Tag& tag() const { return tag_; } allocator_type& allocator() { return *this; } const allocator_type& allocator() const { return *this; } private: Tag tag_; }; class Allocation { public: Allocation(allocator_type& a, // NOLINT(runtime/references) size_type capacity) : capacity_(capacity), buffer_(AllocatorTraits::allocate(a, capacity_)) {} void Dealloc(allocator_type& a) { // NOLINT(runtime/references) AllocatorTraits::deallocate(a, buffer(), capacity()); } size_type capacity() const { return capacity_; } const value_type* buffer() const { return buffer_; } value_type* buffer() { return buffer_; } private: size_type capacity_; value_type* buffer_; }; const Tag& tag() const { return allocator_and_tag_.tag(); } Tag& tag() { return allocator_and_tag_.tag(); } Allocation& allocation() { return reinterpret_cast(rep_.allocation_storage.allocation); } const Allocation& allocation() const { return reinterpret_cast( rep_.allocation_storage.allocation); } void init_allocation(const Allocation& allocation) { new (&rep_.allocation_storage.allocation) Allocation(allocation); } value_type* inlined_space() { return reinterpret_cast(&rep_.inlined_storage.inlined); } const value_type* inlined_space() const { return reinterpret_cast(&rep_.inlined_storage.inlined); } value_type* allocated_space() { return allocation().buffer(); } const value_type* allocated_space() const { return allocation().buffer(); } const allocator_type& allocator() const { return allocator_and_tag_.allocator(); } allocator_type& allocator() { return allocator_and_tag_.allocator(); } bool allocated() const { return tag().allocated(); } // Enlarge the underlying representation so we can store size_ + delta elems. // The size is not changed, and any newly added memory is not initialized. void EnlargeBy(size_type delta); // Shift all elements from position to end() n places to the right. // If the vector needs to be enlarged, memory will be allocated. // Returns iterators pointing to the start of the previously-initialized // portion and the start of the uninitialized portion of the created gap. // The number of initialized spots is pair.second - pair.first; // the number of raw spots is n - (pair.second - pair.first). // // Updates the size of the InlinedVector internally. std::pair ShiftRight(const_iterator position, size_type n); void ResetAllocation(Allocation new_allocation, size_type new_size) { if (allocated()) { Destroy(allocated_space(), allocated_space() + size()); assert(begin() == allocated_space()); allocation().Dealloc(allocator()); allocation() = new_allocation; } else { Destroy(inlined_space(), inlined_space() + size()); init_allocation(new_allocation); // bug: only init once } tag().set_allocated_size(new_size); } template value_type& GrowAndEmplaceBack(Args&&... args) { assert(size() == capacity()); const size_type s = size(); Allocation new_allocation(allocator(), 2 * capacity()); value_type& new_element = Construct(new_allocation.buffer() + s, std::forward(args)...); UninitializedCopy(std::make_move_iterator(data()), std::make_move_iterator(data() + s), new_allocation.buffer()); ResetAllocation(new_allocation, s + 1); return new_element; } void InitAssign(size_type n); void InitAssign(size_type n, const value_type& t); template value_type& Construct(pointer p, Args&&... args) { AllocatorTraits::construct(allocator(), p, std::forward(args)...); return *p; } template void UninitializedCopy(Iter src, Iter src_last, value_type* dst) { for (; src != src_last; ++dst, ++src) Construct(dst, *src); } template void UninitializedFill(value_type* dst, value_type* dst_last, const Args&... args) { for (; dst != dst_last; ++dst) Construct(dst, args...); } // Destroy [ptr, ptr_last) in place. void Destroy(value_type* ptr, value_type* ptr_last); template void AppendRange(Iter first, Iter last, std::input_iterator_tag) { std::copy(first, last, std::back_inserter(*this)); } // Faster path for forward iterators. template void AppendRange(Iter first, Iter last, std::forward_iterator_tag); template void AppendRange(Iter first, Iter last) { using IterTag = typename std::iterator_traits::iterator_category; AppendRange(first, last, IterTag()); } template void AssignRange(Iter first, Iter last, std::input_iterator_tag); // Faster path for forward iterators. template void AssignRange(Iter first, Iter last, std::forward_iterator_tag); template void AssignRange(Iter first, Iter last) { using IterTag = typename std::iterator_traits::iterator_category; AssignRange(first, last, IterTag()); } iterator InsertWithCount(const_iterator position, size_type n, const value_type& v); template iterator InsertWithRange(const_iterator position, InputIter first, InputIter last, std::input_iterator_tag); template iterator InsertWithRange(const_iterator position, ForwardIter first, ForwardIter last, std::forward_iterator_tag); AllocatorAndTag allocator_and_tag_; // Either the inlined or allocated representation union Rep { // Use struct to perform indirection that solves a bizarre compilation // error on Visual Studio (all known versions). struct { typename std::aligned_storage::type inlined[N]; } inlined_storage; struct { typename std::aligned_storage::type allocation; } allocation_storage; } rep_; }; // ----------------------------------------------------------------------------- // InlinedVector Non-Member Functions // ----------------------------------------------------------------------------- // swap() // // Swaps the contents of two inlined vectors. This convenience function // simply calls InlinedVector::swap(other_inlined_vector). template void swap(InlinedVector& a, InlinedVector& b) noexcept(noexcept(a.swap(b))) { a.swap(b); } // operator==() // // Tests the equivalency of the contents of two inlined vectors. template bool operator==(const InlinedVector& a, const InlinedVector& b) { return absl::equal(a.begin(), a.end(), b.begin(), b.end()); } // operator!=() // // Tests the inequality of the contents of two inlined vectors. template bool operator!=(const InlinedVector& a, const InlinedVector& b) { return !(a == b); } // operator<() // // Tests whether the contents of one inlined vector are less than the contents // of another through a lexicographical comparison operation. template bool operator<(const InlinedVector& a, const InlinedVector& b) { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); } // operator>() // // Tests whether the contents of one inlined vector are greater than the // contents of another through a lexicographical comparison operation. template bool operator>(const InlinedVector& a, const InlinedVector& b) { return b < a; } // operator<=() // // Tests whether the contents of one inlined vector are less than or equal to // the contents of another through a lexicographical comparison operation. template bool operator<=(const InlinedVector& a, const InlinedVector& b) { return !(b < a); } // operator>=() // // Tests whether the contents of one inlined vector are greater than or equal to // the contents of another through a lexicographical comparison operation. template bool operator>=(const InlinedVector& a, const InlinedVector& b) { return !(a < b); } // ----------------------------------------------------------------------------- // Implementation of InlinedVector // ----------------------------------------------------------------------------- // // Do not depend on any implementation details below this line. template InlinedVector::InlinedVector(const InlinedVector& v) : allocator_and_tag_(v.allocator()) { reserve(v.size()); if (allocated()) { UninitializedCopy(v.begin(), v.end(), allocated_space()); tag().set_allocated_size(v.size()); } else { UninitializedCopy(v.begin(), v.end(), inlined_space()); tag().set_inline_size(v.size()); } } template InlinedVector::InlinedVector(const InlinedVector& v, const allocator_type& alloc) : allocator_and_tag_(alloc) { reserve(v.size()); if (allocated()) { UninitializedCopy(v.begin(), v.end(), allocated_space()); tag().set_allocated_size(v.size()); } else { UninitializedCopy(v.begin(), v.end(), inlined_space()); tag().set_inline_size(v.size()); } } template InlinedVector::InlinedVector(InlinedVector&& v) noexcept( absl::allocator_is_nothrow::value || std::is_nothrow_move_constructible::value) : allocator_and_tag_(v.allocator_and_tag_) { if (v.allocated()) { // We can just steal the underlying buffer from the source. // That leaves the source empty, so we clear its size. init_allocation(v.allocation()); v.tag() = Tag(); } else { UninitializedCopy(std::make_move_iterator(v.inlined_space()), std::make_move_iterator(v.inlined_space() + v.size()), inlined_space()); } } template InlinedVector::InlinedVector( InlinedVector&& v, const allocator_type& alloc) noexcept(absl::allocator_is_nothrow::value) : allocator_and_tag_(alloc) { if (v.allocated()) { if (alloc == v.allocator()) { // We can just steal the allocation from the source. tag() = v.tag(); init_allocation(v.allocation()); v.tag() = Tag(); } else { // We need to use our own allocator reserve(v.size()); UninitializedCopy(std::make_move_iterator(v.begin()), std::make_move_iterator(v.end()), allocated_space()); tag().set_allocated_size(v.size()); } } else { UninitializedCopy(std::make_move_iterator(v.inlined_space()), std::make_move_iterator(v.inlined_space() + v.size()), inlined_space()); tag().set_inline_size(v.size()); } } template void InlinedVector::InitAssign(size_type n, const value_type& t) { if (n > static_cast(N)) { Allocation new_allocation(allocator(), n); init_allocation(new_allocation); UninitializedFill(allocated_space(), allocated_space() + n, t); tag().set_allocated_size(n); } else { UninitializedFill(inlined_space(), inlined_space() + n, t); tag().set_inline_size(n); } } template void InlinedVector::InitAssign(size_type n) { if (n > static_cast(N)) { Allocation new_allocation(allocator(), n); init_allocation(new_allocation); UninitializedFill(allocated_space(), allocated_space() + n); tag().set_allocated_size(n); } else { UninitializedFill(inlined_space(), inlined_space() + n); tag().set_inline_size(n); } } template void InlinedVector::resize(size_type n) { size_type s = size(); if (n < s) { erase(begin() + n, end()); return; } reserve(n); assert(capacity() >= n); // Fill new space with elements constructed in-place. if (allocated()) { UninitializedFill(allocated_space() + s, allocated_space() + n); tag().set_allocated_size(n); } else { UninitializedFill(inlined_space() + s, inlined_space() + n); tag().set_inline_size(n); } } template void InlinedVector::resize(size_type n, const value_type& elem) { size_type s = size(); if (n < s) { erase(begin() + n, end()); return; } reserve(n); assert(capacity() >= n); // Fill new space with copies of 'elem'. if (allocated()) { UninitializedFill(allocated_space() + s, allocated_space() + n, elem); tag().set_allocated_size(n); } else { UninitializedFill(inlined_space() + s, inlined_space() + n, elem); tag().set_inline_size(n); } } template template typename InlinedVector::iterator InlinedVector::emplace( const_iterator position, Args&&... args) { assert(position >= begin()); assert(position <= end()); if (position == end()) { emplace_back(std::forward(args)...); return end() - 1; } T new_t = T(std::forward(args)...); auto range = ShiftRight(position, 1); if (range.first == range.second) { // constructing into uninitialized memory Construct(range.first, std::move(new_t)); } else { // assigning into moved-from object *range.first = T(std::move(new_t)); } return range.first; } template typename InlinedVector::iterator InlinedVector::erase( const_iterator first, const_iterator last) { assert(begin() <= first); assert(first <= last); assert(last <= end()); iterator range_start = const_cast(first); iterator range_end = const_cast(last); size_type s = size(); ptrdiff_t erase_gap = std::distance(range_start, range_end); if (erase_gap > 0) { pointer space; if (allocated()) { space = allocated_space(); tag().set_allocated_size(s - erase_gap); } else { space = inlined_space(); tag().set_inline_size(s - erase_gap); } std::move(range_end, space + s, range_start); Destroy(space + s - erase_gap, space + s); } return range_start; } template void InlinedVector::swap(InlinedVector& other) { using std::swap; // Augment ADL with std::swap. if (&other == this) { return; } if (allocated() && other.allocated()) { // Both out of line, so just swap the tag, allocation, and allocator. swap(tag(), other.tag()); swap(allocation(), other.allocation()); swap(allocator(), other.allocator()); return; } if (!allocated() && !other.allocated()) { // Both inlined: swap up to smaller size, then move remaining elements. InlinedVector* a = this; InlinedVector* b = &other; if (size() < other.size()) { swap(a, b); } const size_type a_size = a->size(); const size_type b_size = b->size(); assert(a_size >= b_size); // 'a' is larger. Swap the elements up to the smaller array size. std::swap_ranges(a->inlined_space(), a->inlined_space() + b_size, b->inlined_space()); // Move the remaining elements: A[b_size,a_size) -> B[b_size,a_size) b->UninitializedCopy(a->inlined_space() + b_size, a->inlined_space() + a_size, b->inlined_space() + b_size); a->Destroy(a->inlined_space() + b_size, a->inlined_space() + a_size); swap(a->tag(), b->tag()); swap(a->allocator(), b->allocator()); assert(b->size() == a_size); assert(a->size() == b_size); return; } // One is out of line, one is inline. // We first move the elements from the inlined vector into the // inlined space in the other vector. We then put the other vector's // pointer/capacity into the originally inlined vector and swap // the tags. InlinedVector* a = this; InlinedVector* b = &other; if (a->allocated()) { swap(a, b); } assert(!a->allocated()); assert(b->allocated()); const size_type a_size = a->size(); const size_type b_size = b->size(); // In an optimized build, b_size would be unused. (void)b_size; // Made Local copies of size(), don't need tag() accurate anymore swap(a->tag(), b->tag()); // Copy b_allocation out before b's union gets clobbered by inline_space. Allocation b_allocation = b->allocation(); b->UninitializedCopy(a->inlined_space(), a->inlined_space() + a_size, b->inlined_space()); a->Destroy(a->inlined_space(), a->inlined_space() + a_size); a->allocation() = b_allocation; if (a->allocator() != b->allocator()) { swap(a->allocator(), b->allocator()); } assert(b->size() == a_size); assert(a->size() == b_size); } template void InlinedVector::EnlargeBy(size_type delta) { const size_type s = size(); assert(s <= capacity()); size_type target = std::max(static_cast(N), s + delta); // Compute new capacity by repeatedly doubling current capacity // TODO(psrc): Check and avoid overflow? size_type new_capacity = capacity(); while (new_capacity < target) { new_capacity <<= 1; } Allocation new_allocation(allocator(), new_capacity); UninitializedCopy(std::make_move_iterator(data()), std::make_move_iterator(data() + s), new_allocation.buffer()); ResetAllocation(new_allocation, s); } template auto InlinedVector::ShiftRight(const_iterator position, size_type n) -> std::pair { iterator start_used = const_cast(position); iterator start_raw = const_cast(position); size_type s = size(); size_type required_size = s + n; if (required_size > capacity()) { // Compute new capacity by repeatedly doubling current capacity size_type new_capacity = capacity(); while (new_capacity < required_size) { new_capacity <<= 1; } // Move everyone into the new allocation, leaving a gap of n for the // requested shift. Allocation new_allocation(allocator(), new_capacity); size_type index = position - begin(); UninitializedCopy(std::make_move_iterator(data()), std::make_move_iterator(data() + index), new_allocation.buffer()); UninitializedCopy(std::make_move_iterator(data() + index), std::make_move_iterator(data() + s), new_allocation.buffer() + index + n); ResetAllocation(new_allocation, s); // New allocation means our iterator is invalid, so we'll recalculate. // Since the entire gap is in new space, there's no used space to reuse. start_raw = begin() + index; start_used = start_raw; } else { // If we had enough space, it's a two-part move. Elements going into // previously-unoccupied space need an UninitializedCopy. Elements // going into a previously-occupied space are just a move. iterator pos = const_cast(position); iterator raw_space = end(); size_type slots_in_used_space = raw_space - pos; size_type new_elements_in_used_space = std::min(n, slots_in_used_space); size_type new_elements_in_raw_space = n - new_elements_in_used_space; size_type old_elements_in_used_space = slots_in_used_space - new_elements_in_used_space; UninitializedCopy(std::make_move_iterator(pos + old_elements_in_used_space), std::make_move_iterator(raw_space), raw_space + new_elements_in_raw_space); std::move_backward(pos, pos + old_elements_in_used_space, raw_space); // If the gap is entirely in raw space, the used space starts where the raw // space starts, leaving no elements in used space. If the gap is entirely // in used space, the raw space starts at the end of the gap, leaving all // elements accounted for within the used space. start_used = pos; start_raw = pos + new_elements_in_used_space; } tag().add_size(n); return std::make_pair(start_used, start_raw); } template void InlinedVector::Destroy(value_type* ptr, value_type* ptr_last) { for (value_type* p = ptr; p != ptr_last; ++p) { AllocatorTraits::destroy(allocator(), p); } // Overwrite unused memory with 0xab so we can catch uninitialized usage. // Cast to void* to tell the compiler that we don't care that we might be // scribbling on a vtable pointer. #ifndef NDEBUG if (ptr != ptr_last) { memset(reinterpret_cast(ptr), 0xab, sizeof(*ptr) * (ptr_last - ptr)); } #endif } template template void InlinedVector::AppendRange(Iter first, Iter last, std::forward_iterator_tag) { using Length = typename std::iterator_traits::difference_type; Length length = std::distance(first, last); reserve(size() + length); if (allocated()) { UninitializedCopy(first, last, allocated_space() + size()); tag().set_allocated_size(size() + length); } else { UninitializedCopy(first, last, inlined_space() + size()); tag().set_inline_size(size() + length); } } template template void InlinedVector::AssignRange(Iter first, Iter last, std::input_iterator_tag) { // Optimized to avoid reallocation. // Prefer reassignment to copy construction for elements. iterator out = begin(); for ( ; first != last && out != end(); ++first, ++out) *out = *first; erase(out, end()); std::copy(first, last, std::back_inserter(*this)); } template template void InlinedVector::AssignRange(Iter first, Iter last, std::forward_iterator_tag) { using Length = typename std::iterator_traits::difference_type; Length length = std::distance(first, last); // Prefer reassignment to copy construction for elements. if (static_cast(length) <= size()) { erase(std::copy(first, last, begin()), end()); return; } reserve(length); iterator out = begin(); for (; out != end(); ++first, ++out) *out = *first; if (allocated()) { UninitializedCopy(first, last, out); tag().set_allocated_size(length); } else { UninitializedCopy(first, last, out); tag().set_inline_size(length); } } template auto InlinedVector::InsertWithCount(const_iterator position, size_type n, const value_type& v) -> iterator { assert(position >= begin() && position <= end()); if (n == 0) return const_cast(position); value_type copy = v; std::pair it_pair = ShiftRight(position, n); std::fill(it_pair.first, it_pair.second, copy); UninitializedFill(it_pair.second, it_pair.first + n, copy); return it_pair.first; } template template auto InlinedVector::InsertWithRange(const_iterator position, InputIter first, InputIter last, std::input_iterator_tag) -> iterator { assert(position >= begin() && position <= end()); size_type index = position - cbegin(); size_type i = index; while (first != last) insert(begin() + i++, *first++); return begin() + index; } // Overload of InlinedVector::InsertWithRange() template template auto InlinedVector::InsertWithRange(const_iterator position, ForwardIter first, ForwardIter last, std::forward_iterator_tag) -> iterator { assert(position >= begin() && position <= end()); if (first == last) { return const_cast(position); } using Length = typename std::iterator_traits::difference_type; Length n = std::distance(first, last); std::pair it_pair = ShiftRight(position, n); size_type used_spots = it_pair.second - it_pair.first; ForwardIter open_spot = std::next(first, used_spots); std::copy(first, open_spot, it_pair.first); UninitializedCopy(open_spot, last, it_pair.second); return it_pair.first; } } // inline namespace lts_2018_06_20 } // namespace absl #endif // ABSL_CONTAINER_INLINED_VECTOR_H_