/* * Copyright 2020 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. */ #pragma once #include #include #include #include #include #include #include #include namespace android::ftl { constexpr struct IteratorRangeTag { } kIteratorRange; // Fixed-capacity, statically allocated counterpart of std::vector. Like std::array, StaticVector // allocates contiguous storage for N elements of type T at compile time, but stores at most (rather // than exactly) N elements. Unlike std::array, its default constructor does not require T to have a // default constructor, since elements are constructed in place as the vector grows. Operations that // insert an element (emplace_back, push_back, etc.) fail when the vector is full. The API otherwise // adheres to standard containers, except the unstable_erase operation that does not preserve order, // and the replace operation that destructively emplaces. // // StaticVector is analogous to an iterable std::optional. // StaticVector is an error. // // Example usage: // // ftl::StaticVector vector; // assert(vector.empty()); // // vector = {'a', 'b'}; // assert(vector.size() == 2u); // // vector.push_back('c'); // assert(vector.full()); // // assert(!vector.push_back('d')); // assert(vector.size() == 3u); // // vector.unstable_erase(vector.begin()); // assert(vector == (ftl::StaticVector{'c', 'b'})); // // vector.pop_back(); // assert(vector.back() == 'c'); // // const char array[] = "hi"; // vector = ftl::StaticVector(array); // assert(vector == (ftl::StaticVector{'h', 'i', '\0'})); // // ftl::StaticVector strings = ftl::init::list("abc")("123456", 3u)(3u, '?'); // assert(strings.size() == 3u); // assert(strings[0] == "abc"); // assert(strings[1] == "123"); // assert(strings[2] == "???"); // template class StaticVector final : ArrayTraits, ArrayIterators, T>, ArrayComparators { static_assert(N > 0); using ArrayTraits::construct_at; using Iter = ArrayIterators; friend Iter; // There is ambiguity when constructing from two iterator-like elements like pointers: // they could be an iterator range, or arguments for in-place construction. Assume the // latter unless they are input iterators and cannot be used to construct elements. If // the former is intended, the caller can pass an IteratorRangeTag to disambiguate. template > using is_input_iterator = std::conjunction, std::negation>>; public: FTL_ARRAY_TRAIT(T, value_type); FTL_ARRAY_TRAIT(T, size_type); FTL_ARRAY_TRAIT(T, difference_type); FTL_ARRAY_TRAIT(T, pointer); FTL_ARRAY_TRAIT(T, reference); FTL_ARRAY_TRAIT(T, iterator); FTL_ARRAY_TRAIT(T, reverse_iterator); FTL_ARRAY_TRAIT(T, const_pointer); FTL_ARRAY_TRAIT(T, const_reference); FTL_ARRAY_TRAIT(T, const_iterator); FTL_ARRAY_TRAIT(T, const_reverse_iterator); // Creates an empty vector. StaticVector() = default; // Copies and moves a vector, respectively. StaticVector(const StaticVector& other) : StaticVector(kIteratorRange, other.begin(), other.end()) {} StaticVector(StaticVector&& other) { swap(other); } // Copies at most N elements from a smaller convertible vector. template > StaticVector(const StaticVector& other) : StaticVector(kIteratorRange, other.begin(), other.end()) {} // Copies at most N elements from an array. template explicit StaticVector(U (&array)[M]) : StaticVector(kIteratorRange, std::begin(array), std::end(array)) {} // Copies at most N elements from the range [first, last). // // IteratorRangeTag disambiguates with initialization from two iterator-like elements. // template {}>> StaticVector(Iterator first, Iterator last) : StaticVector(kIteratorRange, first, last) { using V = typename std::iterator_traits::value_type; static_assert(std::is_constructible_v, "Incompatible iterator range"); } template StaticVector(IteratorRangeTag, Iterator first, Iterator last) : size_(std::min(max_size(), static_cast(std::distance(first, last)))) { std::uninitialized_copy(first, first + size_, begin()); } // Constructs at most N elements. The template arguments T and N are inferred using the // deduction guide defined below. Note that T is determined from the first element, and // subsequent elements must have convertible types: // // ftl::StaticVector vector = {1, 2, 3}; // static_assert(std::is_same_v>); // // const auto copy = "quince"s; // auto move = "tart"s; // ftl::StaticVector vector = {copy, std::move(move)}; // // static_assert(std::is_same_v>); // template >> StaticVector(E&& element, Es&&... elements) : StaticVector(std::index_sequence<0>{}, std::forward(element), std::forward(elements)...) { static_assert(sizeof...(elements) < N, "Too many elements"); } // Constructs at most N elements in place by forwarding per-element constructor arguments. The // template arguments T and N are inferred using the deduction guide defined below. The syntax // for listing arguments is as follows: // // ftl::StaticVector vector = ftl::init::list("abc")()(3u, '?'); // // static_assert(std::is_same_v>); // assert(vector.full()); // assert(vector[0] == "abc"); // assert(vector[1].empty()); // assert(vector[2] == "???"); // template StaticVector(InitializerList, Types...>&& list) : StaticVector(std::index_sequence<0, 0, Size>{}, std::make_index_sequence{}, std::index_sequence{}, list.tuple) {} ~StaticVector() { std::destroy(begin(), end()); } StaticVector& operator=(const StaticVector& other) { StaticVector copy(other); swap(copy); return *this; } StaticVector& operator=(StaticVector&& other) { std::destroy(begin(), end()); size_ = 0; swap(other); return *this; } // IsEmpty enables a fast path when the vector is known to be empty at compile time. template void swap(StaticVector&); static constexpr size_type max_size() { return N; } size_type size() const { return size_; } bool empty() const { return size() == 0; } bool full() const { return size() == max_size(); } iterator begin() { return std::launder(reinterpret_cast(data_)); } iterator end() { return begin() + size(); } using Iter::begin; using Iter::end; using Iter::cbegin; using Iter::cend; using Iter::rbegin; using Iter::rend; using Iter::crbegin; using Iter::crend; using Iter::last; using Iter::back; using Iter::front; using Iter::operator[]; // Replaces an element, and returns a reference to it. The iterator must be dereferenceable, so // replacing at end() is erroneous. // // The element is emplaced via move constructor, so type T does not need to define copy/move // assignment, e.g. its data members may be const. // // The arguments may directly or indirectly refer to the element being replaced. // // Iterators to the replaced element point to its replacement, and others remain valid. // template reference replace(const_iterator it, Args&&... args) { value_type element{std::forward(args)...}; std::destroy_at(it); // This is only safe because exceptions are disabled. return *construct_at(it, std::move(element)); } // Appends an element, and returns an iterator to it. If the vector is full, the element is not // inserted, and the end() iterator is returned. // // On success, the end() iterator is invalidated. // template iterator emplace_back(Args&&... args) { if (full()) return end(); const iterator it = construct_at(end(), std::forward(args)...); ++size_; return it; } // Appends an element unless the vector is full, and returns whether the element was inserted. // // On success, the end() iterator is invalidated. // bool push_back(const value_type& v) { // Two statements for sequence point. const iterator it = emplace_back(v); return it != end(); } bool push_back(value_type&& v) { // Two statements for sequence point. const iterator it = emplace_back(std::move(v)); return it != end(); } // Removes the last element. The vector must not be empty, or the call is erroneous. // // The last() and end() iterators are invalidated. // void pop_back() { unstable_erase(last()); } // Erases an element, but does not preserve order. Rather than shifting subsequent elements, // this moves the last element to the slot of the erased element. // // The last() and end() iterators, as well as those to the erased element, are invalidated. // void unstable_erase(const_iterator it) { std::destroy_at(it); if (it != last()) { // Move last element and destroy its source for destructor side effects. This is only // safe because exceptions are disabled. construct_at(it, std::move(back())); std::destroy_at(last()); } --size_; } private: // Recursion for variadic constructor. template StaticVector(std::index_sequence, E&& element, Es&&... elements) : StaticVector(std::index_sequence{}, std::forward(elements)...) { construct_at(begin() + I, std::forward(element)); } // Base case for variadic constructor. template explicit StaticVector(std::index_sequence) : size_(I) {} // Recursion for in-place constructor. // // Construct element I by extracting its arguments from the InitializerList tuple. ArgIndex // is the position of its first argument in Args, and ArgCount is the number of arguments. // The Indices sequence corresponds to [0, ArgCount). // // The Sizes sequence lists the argument counts for elements after I, so Size is the ArgCount // for the next element. The recursion stops when Sizes is empty for the last element. // template StaticVector(std::index_sequence, std::index_sequence, std::index_sequence, std::tuple& tuple) : StaticVector(std::index_sequence{}, std::make_index_sequence{}, std::index_sequence{}, tuple) { construct_at(begin() + I, std::move(std::get(tuple))...); } // Base case for in-place constructor. template StaticVector(std::index_sequence, std::index_sequence, std::index_sequence<>, std::tuple& tuple) : size_(I + 1) { construct_at(begin() + I, std::move(std::get(tuple))...); } size_type size_ = 0; std::aligned_storage_t data_[N]; }; // Deduction guide for array constructor. template StaticVector(T (&)[N]) -> StaticVector, N>; // Deduction guide for variadic constructor. template , typename = std::enable_if_t<(std::is_constructible_v && ...)>> StaticVector(T&&, Us&&...) -> StaticVector; // Deduction guide for in-place constructor. template StaticVector(InitializerList, Types...>&&) -> StaticVector; template template void StaticVector::swap(StaticVector& other) { auto [to, from] = std::make_pair(this, &other); if (from == this) return; // Assume this vector has fewer elements, so the excess of the other vector will be moved to it. auto [min, max] = std::make_pair(size(), other.size()); // No elements to swap if moving into an empty vector. if constexpr (IsEmpty) { assert(min == 0); } else { if (min > max) { std::swap(from, to); std::swap(min, max); } // Swap elements [0, min). std::swap_ranges(begin(), begin() + min, other.begin()); // No elements to move if sizes are equal. if (min == max) return; } // Move elements [min, max) and destroy their source for destructor side effects. const auto [first, last] = std::make_pair(from->begin() + min, from->begin() + max); std::uninitialized_move(first, last, to->begin() + min); std::destroy(first, last); std::swap(size_, other.size_); } template inline void swap(StaticVector& lhs, StaticVector& rhs) { lhs.swap(rhs); } } // namespace android::ftl