FTL: Add SmallVector<T, N>

SmallVector is a StaticVector that promotes to std::vector when full.
It will serve as a base for SmallMap.

Bug: 160012986
Test: ftl_test
Change-Id: Id223a6fc169d792746220d2dde1f9f1a0e87a0cc
diff --git a/include/ftl/ArrayTraits.h b/include/ftl/ArrayTraits.h
new file mode 100644
index 0000000..28f717a
--- /dev/null
+++ b/include/ftl/ArrayTraits.h
@@ -0,0 +1,129 @@
+/*
+ * 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 <algorithm>
+#include <iterator>
+#include <new>
+
+#define FTL_ARRAY_TRAIT(T, U) using U = typename ArrayTraits<T>::U
+
+namespace android::ftl {
+
+template <typename T>
+struct ArrayTraits {
+    using value_type = T;
+    using size_type = size_t;
+    using difference_type = ptrdiff_t;
+
+    using pointer = value_type*;
+    using reference = value_type&;
+    using iterator = pointer;
+    using reverse_iterator = std::reverse_iterator<iterator>;
+
+    using const_pointer = const value_type*;
+    using const_reference = const value_type&;
+    using const_iterator = const_pointer;
+    using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+    // TODO: Replace with std::construct_at in C++20.
+    template <typename... Args>
+    static pointer construct_at(const_iterator it, Args&&... args) {
+        void* const ptr = const_cast<void*>(static_cast<const void*>(it));
+        return new (ptr) value_type{std::forward<Args>(args)...};
+    }
+};
+
+// CRTP mixin to define iterator functions in terms of non-const Self::begin and Self::end.
+template <typename Self, typename T>
+class ArrayIterators {
+    FTL_ARRAY_TRAIT(T, size_type);
+
+    FTL_ARRAY_TRAIT(T, reference);
+    FTL_ARRAY_TRAIT(T, iterator);
+    FTL_ARRAY_TRAIT(T, reverse_iterator);
+
+    FTL_ARRAY_TRAIT(T, const_reference);
+    FTL_ARRAY_TRAIT(T, const_iterator);
+    FTL_ARRAY_TRAIT(T, const_reverse_iterator);
+
+    Self& self() const { return *const_cast<Self*>(static_cast<const Self*>(this)); }
+
+public:
+    const_iterator begin() const { return cbegin(); }
+    const_iterator cbegin() const { return self().begin(); }
+
+    const_iterator end() const { return cend(); }
+    const_iterator cend() const { return self().end(); }
+
+    reverse_iterator rbegin() { return std::make_reverse_iterator(self().end()); }
+    const_reverse_iterator rbegin() const { return crbegin(); }
+    const_reverse_iterator crbegin() const { return self().rbegin(); }
+
+    reverse_iterator rend() { return std::make_reverse_iterator(self().begin()); }
+    const_reverse_iterator rend() const { return crend(); }
+    const_reverse_iterator crend() const { return self().rend(); }
+
+    iterator last() { return self().end() - 1; }
+    const_iterator last() const { return self().last(); }
+
+    reference front() { return *self().begin(); }
+    const_reference front() const { return self().front(); }
+
+    reference back() { return *last(); }
+    const_reference back() const { return self().back(); }
+
+    reference operator[](size_type i) { return *(self().begin() + i); }
+    const_reference operator[](size_type i) const { return self()[i]; }
+};
+
+// Mixin to define comparison operators for an array-like template.
+// TODO: Replace with operator<=> in C++20.
+template <template <typename, size_t> class Array>
+struct ArrayComparators {
+    template <typename T, size_t N, size_t M>
+    friend bool operator==(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
+    }
+
+    template <typename T, size_t N, size_t M>
+    friend bool operator<(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
+    }
+
+    template <typename T, size_t N, size_t M>
+    friend bool operator>(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return rhs < lhs;
+    }
+
+    template <typename T, size_t N, size_t M>
+    friend bool operator!=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return !(lhs == rhs);
+    }
+
+    template <typename T, size_t N, size_t M>
+    friend bool operator>=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return !(lhs < rhs);
+    }
+
+    template <typename T, size_t N, size_t M>
+    friend bool operator<=(const Array<T, N>& lhs, const Array<T, M>& rhs) {
+        return !(lhs > rhs);
+    }
+};
+
+} // namespace android::ftl
diff --git a/include/ftl/SmallVector.h b/include/ftl/SmallVector.h
new file mode 100644
index 0000000..b632bb2
--- /dev/null
+++ b/include/ftl/SmallVector.h
@@ -0,0 +1,381 @@
+/*
+ * 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 <ftl/ArrayTraits.h>
+#include <ftl/StaticVector.h>
+
+#include <algorithm>
+#include <iterator>
+#include <type_traits>
+#include <utility>
+#include <variant>
+#include <vector>
+
+namespace android::ftl {
+
+template <typename>
+struct IsSmallVector;
+
+// ftl::StaticVector that promotes to std::vector when full. SmallVector is a drop-in replacement
+// for std::vector with statically allocated storage for N elements, whose goal is to improve run
+// time by avoiding heap allocation and increasing probability of cache hits. The standard API is
+// augmented by an unstable_erase operation that does not preserve order, and a replace operation
+// that destructively emplaces.
+//
+// SmallVector<T, 0> is a specialization that thinly wraps std::vector.
+//
+// Example usage:
+//
+//     ftl::SmallVector<char, 3> vector;
+//     assert(vector.empty());
+//     assert(!vector.dynamic());
+//
+//     vector = {'a', 'b', 'c'};
+//     assert(vector.size() == 3u);
+//     assert(!vector.dynamic());
+//
+//     vector.push_back('d');
+//     assert(vector.dynamic());
+//
+//     vector.unstable_erase(vector.begin());
+//     assert(vector == (ftl::SmallVector{'d', 'b', 'c'}));
+//
+//     vector.pop_back();
+//     assert(vector.back() == 'b');
+//     assert(vector.dynamic());
+//
+//     const char array[] = "hi";
+//     vector = ftl::SmallVector(array);
+//     assert(vector == (ftl::SmallVector{'h', 'i', '\0'}));
+//     assert(!vector.dynamic());
+//
+template <typename T, size_t N>
+class SmallVector final : ArrayTraits<T>, ArrayComparators<SmallVector> {
+    using Static = StaticVector<T, N>;
+    using Dynamic = SmallVector<T, 0>;
+
+    // TODO: Replace with std::remove_cvref_t in C++20.
+    template <typename U>
+    using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<U>>;
+
+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.
+    SmallVector() = default;
+
+    // Constructs at most N elements. See StaticVector for underlying constructors.
+    template <typename Arg, typename... Args,
+              typename = std::enable_if_t<!IsSmallVector<remove_cvref_t<Arg>>{}>>
+    SmallVector(Arg&& arg, Args&&... args)
+          : mVector(std::in_place_type<Static>, std::forward<Arg>(arg),
+                    std::forward<Args>(args)...) {}
+
+    // Copies at most N elements from a smaller convertible vector.
+    template <typename U, size_t M, typename = std::enable_if_t<M <= N>>
+    SmallVector(const SmallVector<U, M>& other)
+          : SmallVector(IteratorRange, other.begin(), other.end()) {}
+
+    void swap(SmallVector& other) { mVector.swap(other.mVector); }
+
+    // Returns whether the vector is backed by static or dynamic storage.
+    bool dynamic() const { return std::holds_alternative<Dynamic>(mVector); }
+
+#define VISITOR(T, F, ...)                                               \
+    T F() __VA_ARGS__ {                                                  \
+        return std::visit([](auto&& v) -> T { return v.F(); }, mVector); \
+    }
+
+    VISITOR(size_type, max_size, const)
+    VISITOR(size_type, size, const)
+    VISITOR(bool, empty, const)
+
+    // noexcept to suppress warning about zero variadic macro arguments.
+    VISITOR(iterator, begin, noexcept)
+    VISITOR(const_iterator, begin, const)
+    VISITOR(const_iterator, cbegin, const)
+
+    VISITOR(iterator, end, noexcept)
+    VISITOR(const_iterator, end, const)
+    VISITOR(const_iterator, cend, const)
+
+    VISITOR(reverse_iterator, rbegin, noexcept)
+    VISITOR(const_reverse_iterator, rbegin, const)
+    VISITOR(const_reverse_iterator, crbegin, const)
+
+    VISITOR(reverse_iterator, rend, noexcept)
+    VISITOR(const_reverse_iterator, rend, const)
+    VISITOR(const_reverse_iterator, crend, const)
+
+    VISITOR(iterator, last, noexcept)
+    VISITOR(const_iterator, last, const)
+
+    VISITOR(reference, front, noexcept)
+    VISITOR(const_reference, front, const)
+
+    VISITOR(reference, back, noexcept)
+    VISITOR(const_reference, back, const)
+
+#undef VISITOR
+
+    reference operator[](size_type i) {
+        return std::visit([i](auto& v) -> reference { return v[i]; }, mVector);
+    }
+
+    const_reference operator[](size_type i) const {
+        return std::visit([i](const auto& v) -> const_reference { return v[i]; }, mVector);
+    }
+
+    // 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 <typename... Args>
+    reference replace(const_iterator it, Args&&... args) {
+        return std::
+                visit([it, &args...](auto& v)
+                              -> reference { return v.replace(it, std::forward<Args>(args)...); },
+                      mVector);
+    }
+
+    // Appends an element, and returns a reference to it.
+    //
+    // If the vector reaches its static or dynamic capacity, then all iterators are invalidated.
+    // Otherwise, only the end() iterator is invalidated.
+    //
+    template <typename... Args>
+    reference emplace_back(Args&&... args) {
+        constexpr auto insertStatic = &Static::template emplace_back<Args...>;
+        constexpr auto insertDynamic = &Dynamic::template emplace_back<Args...>;
+        return *insert<insertStatic, insertDynamic>(std::forward<Args>(args)...);
+    }
+
+    // Appends an element.
+    //
+    // If the vector reaches its static or dynamic capacity, then all iterators are invalidated.
+    // Otherwise, only the end() iterator is invalidated.
+    //
+    void push_back(const value_type& v) {
+        constexpr auto insertStatic =
+                static_cast<bool (Static::*)(const value_type&)>(&Static::push_back);
+        constexpr auto insertDynamic =
+                static_cast<bool (Dynamic::*)(const value_type&)>(&Dynamic::push_back);
+        insert<insertStatic, insertDynamic>(v);
+    }
+
+    void push_back(value_type&& v) {
+        constexpr auto insertStatic =
+                static_cast<bool (Static::*)(value_type&&)>(&Static::push_back);
+        constexpr auto insertDynamic =
+                static_cast<bool (Dynamic::*)(value_type&&)>(&Dynamic::push_back);
+        insert<insertStatic, insertDynamic>(std::move(v));
+    }
+
+    // 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() {
+        std::visit([](auto& v) { v.pop_back(); }, mVector);
+    }
+
+    // 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(iterator it) {
+        std::visit([it](auto& v) { v.unstable_erase(it); }, mVector);
+    }
+
+private:
+    template <typename... Vs>
+    struct Visitor : Vs... {};
+
+    // TODO: Remove this deduction guide in C++20.
+    template <typename... Vs>
+    Visitor(Vs...) -> Visitor<Vs...>;
+
+    template <auto insertStatic, auto insertDynamic, typename... Args>
+    auto insert(Args&&... args) {
+        return std::visit(Visitor{[this, &args...](Static& vector) {
+                                      if (vector.full()) {
+                                          return (promote(vector).*
+                                                  insertDynamic)(std::forward<Args>(args)...);
+                                      }
+
+                                      return (vector.*insertStatic)(std::forward<Args>(args)...);
+                                  },
+                                  [&args...](Dynamic& vector) {
+                                      return (vector.*insertDynamic)(std::forward<Args>(args)...);
+                                  }},
+                          mVector);
+    }
+
+    Dynamic& promote(Static& staticVector) {
+        assert(staticVector.full());
+
+        // Allocate double capacity to reduce probability of reallocation.
+        Dynamic vector;
+        vector.reserve(Static::max_size() * 2);
+        std::move(staticVector.begin(), staticVector.end(), std::back_inserter(vector));
+
+        return mVector.template emplace<Dynamic>(std::move(vector));
+    }
+
+    std::variant<Static, Dynamic> mVector;
+};
+
+// Partial specialization without static storage.
+template <typename T>
+class SmallVector<T, 0> final : ArrayTraits<T>,
+                                ArrayIterators<SmallVector<T, 0>, T>,
+                                std::vector<T> {
+    using ArrayTraits<T>::construct_at;
+
+    using Iter = ArrayIterators<SmallVector, T>;
+    using Impl = std::vector<T>;
+
+    friend Iter;
+
+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);
+
+    using Impl::Impl;
+
+    using Impl::empty;
+    using Impl::max_size;
+    using Impl::size;
+
+    using Impl::reserve;
+
+    // std::vector iterators are not necessarily raw pointers.
+    iterator begin() { return Impl::data(); }
+    iterator end() { return Impl::data() + 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[];
+
+    template <typename... Args>
+    reference replace(const_iterator it, Args&&... args) {
+        value_type element{std::forward<Args>(args)...};
+        std::destroy_at(it);
+        // This is only safe because exceptions are disabled.
+        return *construct_at(it, std::move(element));
+    }
+
+    template <typename... Args>
+    iterator emplace_back(Args&&... args) {
+        return &Impl::emplace_back(std::forward<Args>(args)...);
+    }
+
+    bool push_back(const value_type& v) {
+        Impl::push_back(v);
+        return true;
+    }
+
+    bool push_back(value_type&& v) {
+        Impl::push_back(std::move(v));
+        return true;
+    }
+
+    using Impl::pop_back;
+
+    void unstable_erase(iterator it) {
+        if (it != last()) std::iter_swap(it, last());
+        pop_back();
+    }
+
+    void swap(SmallVector& other) { Impl::swap(other); }
+};
+
+template <typename>
+struct IsSmallVector : std::false_type {};
+
+template <typename T, size_t N>
+struct IsSmallVector<SmallVector<T, N>> : std::true_type {};
+
+// Deduction guide for array constructor.
+template <typename T, size_t N>
+SmallVector(T (&)[N]) -> SmallVector<std::remove_cv_t<T>, N>;
+
+// Deduction guide for variadic constructor.
+template <typename T, typename... Us, typename V = std::decay_t<T>,
+          typename = std::enable_if_t<(std::is_constructible_v<V, Us> && ...)>>
+SmallVector(T&&, Us&&...) -> SmallVector<V, 1 + sizeof...(Us)>;
+
+// Deduction guide for in-place constructor.
+template <typename T, typename... Us>
+SmallVector(std::in_place_type_t<T>, Us&&...) -> SmallVector<T, sizeof...(Us)>;
+
+// Deduction guide for StaticVector conversion.
+template <typename T, size_t N>
+SmallVector(StaticVector<T, N>&&) -> SmallVector<T, N>;
+
+template <typename T, size_t N>
+inline void swap(SmallVector<T, N>& lhs, SmallVector<T, N>& rhs) {
+    lhs.swap(rhs);
+}
+
+} // namespace android::ftl
diff --git a/include/ftl/StaticVector.h b/include/ftl/StaticVector.h
index c0cdd11..457095d 100644
--- a/include/ftl/StaticVector.h
+++ b/include/ftl/StaticVector.h
@@ -16,11 +16,12 @@
 
 #pragma once
 
+#include <ftl/ArrayTraits.h>
+
 #include <algorithm>
 #include <cassert>
 #include <iterator>
 #include <memory>
-#include <new>
 #include <type_traits>
 #include <utility>
 
@@ -63,9 +64,16 @@
 //     assert(vector == (ftl::StaticVector{'h', 'i', '\0'}));
 //
 template <typename T, size_t N>
-class StaticVector final {
+class StaticVector final : ArrayTraits<T>,
+                           ArrayIterators<StaticVector<T, N>, T>,
+                           ArrayComparators<StaticVector> {
     static_assert(N > 0);
 
+    using ArrayTraits<T>::construct_at;
+
+    using Iter = ArrayIterators<StaticVector, T>;
+    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
@@ -76,19 +84,19 @@
             std::negation<std::is_constructible<T, I>>>;
 
 public:
-    using value_type = T;
-    using size_type = size_t;
-    using difference_type = ptrdiff_t;
+    FTL_ARRAY_TRAIT(T, value_type);
+    FTL_ARRAY_TRAIT(T, size_type);
+    FTL_ARRAY_TRAIT(T, difference_type);
 
-    using pointer = value_type*;
-    using reference = value_type&;
-    using iterator = pointer;
-    using reverse_iterator = std::reverse_iterator<iterator>;
+    FTL_ARRAY_TRAIT(T, pointer);
+    FTL_ARRAY_TRAIT(T, reference);
+    FTL_ARRAY_TRAIT(T, iterator);
+    FTL_ARRAY_TRAIT(T, reverse_iterator);
 
-    using const_pointer = const value_type*;
-    using const_reference = const value_type&;
-    using const_iterator = const_pointer;
-    using const_reverse_iterator = std::reverse_iterator<const_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;
@@ -180,32 +188,26 @@
     bool full() const { return size() == max_size(); }
 
     iterator begin() { return std::launder(reinterpret_cast<pointer>(mData)); }
-    const_iterator begin() const { return cbegin(); }
-    const_iterator cbegin() const { return mut().begin(); }
-
     iterator end() { return begin() + size(); }
-    const_iterator end() const { return cend(); }
-    const_iterator cend() const { return mut().end(); }
 
-    reverse_iterator rbegin() { return std::make_reverse_iterator(end()); }
-    const_reverse_iterator rbegin() const { return crbegin(); }
-    const_reverse_iterator crbegin() const { return mut().rbegin(); }
+    using Iter::begin;
+    using Iter::end;
 
-    reverse_iterator rend() { return std::make_reverse_iterator(begin()); }
-    const_reverse_iterator rend() const { return crend(); }
-    const_reverse_iterator crend() const { return mut().rend(); }
+    using Iter::cbegin;
+    using Iter::cend;
 
-    iterator last() { return end() - 1; }
-    const_iterator last() const { return mut().last(); }
+    using Iter::rbegin;
+    using Iter::rend;
 
-    reference front() { return *begin(); }
-    const_reference front() const { return mut().front(); }
+    using Iter::crbegin;
+    using Iter::crend;
 
-    reference back() { return *last(); }
-    const_reference back() const { return mut().back(); }
+    using Iter::last;
 
-    reference operator[](size_type i) { return *(begin() + i); }
-    const_reference operator[](size_type i) const { return mut()[i]; }
+    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.
@@ -242,7 +244,13 @@
     //
     // On success, the end() iterator is invalidated.
     //
-    bool push_back(value_type v) {
+    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();
@@ -273,8 +281,6 @@
 private:
     struct Empty {};
 
-    StaticVector& mut() const { return *const_cast<StaticVector*>(this); }
-
     // Recursion for variadic constructor.
     template <size_t I, typename E, typename... Es>
     StaticVector(std::index_sequence<I>, E&& element, Es&&... elements)
@@ -286,13 +292,6 @@
     template <size_t I>
     explicit StaticVector(std::index_sequence<I>) : mSize(I) {}
 
-    // TODO: Replace with std::construct_at in C++20.
-    template <typename... Args>
-    static pointer construct_at(const_iterator it, Args&&... args) {
-        void* const ptr = const_cast<void*>(static_cast<const void*>(it));
-        return new (ptr) value_type{std::forward<Args>(args)...};
-    }
-
     size_type mSize = 0;
     std::aligned_storage_t<sizeof(value_type), alignof(value_type)> mData[N];
 };
@@ -348,35 +347,4 @@
     lhs.swap(rhs);
 }
 
-// TODO: Replace with operator<=> in C++20.
-template <typename T, size_t N, size_t M>
-inline bool operator==(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
-}
-
-template <typename T, size_t N, size_t M>
-inline bool operator<(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
-}
-
-template <typename T, size_t N, size_t M>
-inline bool operator>(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return rhs < lhs;
-}
-
-template <typename T, size_t N, size_t M>
-inline bool operator!=(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return !(lhs == rhs);
-}
-
-template <typename T, size_t N, size_t M>
-inline bool operator>=(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return !(lhs < rhs);
-}
-
-template <typename T, size_t N, size_t M>
-inline bool operator<=(const StaticVector<T, N>& lhs, const StaticVector<T, M>& rhs) {
-    return !(rhs < lhs);
-}
-
 } // namespace android::ftl
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index e11be57..fb32382 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -5,6 +5,7 @@
         address: true,
     },
     srcs: [
+        "SmallVector_test.cpp",
         "StaticVector_test.cpp",
     ],
     cflags: [
diff --git a/libs/ftl/SmallVector_test.cpp b/libs/ftl/SmallVector_test.cpp
new file mode 100644
index 0000000..c41e622
--- /dev/null
+++ b/libs/ftl/SmallVector_test.cpp
@@ -0,0 +1,455 @@
+/*
+ * 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.
+ */
+
+#include <ftl/SmallVector.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <iterator>
+#include <string>
+#include <utility>
+
+using namespace std::string_literals;
+
+namespace android::test {
+
+using ftl::SmallVector;
+
+// Keep in sync with example usage in header file.
+TEST(SmallVector, Example) {
+    ftl::SmallVector<char, 3> vector;
+    EXPECT_TRUE(vector.empty());
+    EXPECT_FALSE(vector.dynamic());
+
+    vector = {'a', 'b', 'c'};
+    EXPECT_EQ(vector.size(), 3u);
+    EXPECT_FALSE(vector.dynamic());
+
+    vector.push_back('d');
+    EXPECT_TRUE(vector.dynamic());
+
+    vector.unstable_erase(vector.begin());
+    EXPECT_EQ(vector, (ftl::SmallVector{'d', 'b', 'c'}));
+
+    vector.pop_back();
+    EXPECT_EQ(vector.back(), 'b');
+    EXPECT_TRUE(vector.dynamic());
+
+    const char array[] = "hi";
+    vector = ftl::SmallVector(array);
+    EXPECT_EQ(vector, (ftl::SmallVector{'h', 'i', '\0'}));
+    EXPECT_FALSE(vector.dynamic());
+}
+
+TEST(SmallVector, Construct) {
+    {
+        // Default constructor.
+        SmallVector<std::string, 2> vector;
+
+        EXPECT_TRUE(vector.empty());
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // Array constructor.
+        const float kFloats[] = {.1f, .2f, .3f};
+        SmallVector vector(kFloats);
+
+        EXPECT_EQ(vector, (SmallVector{.1f, .2f, .3f}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // Iterator constructor.
+        const char chars[] = "abcdef";
+        std::string string(chars);
+        SmallVector<char, sizeof(chars)> vector(string.begin(), string.end());
+
+        EXPECT_STREQ(vector.begin(), chars);
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // Variadic constructor with same types.
+        SmallVector vector = {1, 2, 3};
+
+        static_assert(std::is_same_v<decltype(vector), SmallVector<int, 3>>);
+        EXPECT_EQ(vector, (SmallVector{1, 2, 3}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // Variadic constructor with different types.
+        const auto copy = "quince"s;
+        auto move = "tart"s;
+        SmallVector vector = {copy, std::move(move)};
+
+        static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 2>>);
+        EXPECT_EQ(vector, (SmallVector{"quince"s, "tart"s}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // In-place constructor with same types.
+        SmallVector vector(std::in_place_type<std::string>, "red", "velvet", "cake");
+
+        static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 3>>);
+        EXPECT_EQ(vector, (SmallVector{"red"s, "velvet"s, "cake"s}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // In-place constructor with different types.
+        const auto copy = "red"s;
+        auto move = "velvet"s;
+        std::initializer_list<char> list = {'c', 'a', 'k', 'e'};
+        SmallVector vector(std::in_place_type<std::string>, copy.c_str(), std::move(move), list);
+
+        static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 3>>);
+        EXPECT_EQ(vector, (SmallVector{"red"s, "velvet"s, "cake"s}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+    {
+        // Conversion from StaticVector.
+        ftl::StaticVector doubles = {.1, .2, .3};
+        SmallVector vector = std::move(doubles);
+        EXPECT_TRUE(doubles.empty());
+
+        static_assert(std::is_same_v<decltype(vector), SmallVector<double, 3>>);
+        EXPECT_EQ(vector, (SmallVector{.1, .2, .3}));
+        EXPECT_FALSE(vector.dynamic());
+    }
+}
+
+TEST(SmallVector, String) {
+    SmallVector<char, 10> chars;
+    char c = 'a';
+    std::generate_n(std::back_inserter(chars), chars.max_size(), [&c] { return c++; });
+    chars.push_back('\0');
+
+    EXPECT_TRUE(chars.dynamic());
+    EXPECT_EQ(chars.size(), 11u);
+    EXPECT_STREQ(chars.begin(), "abcdefghij");
+
+    // Constructor takes iterator range.
+    const char kString[] = "123456";
+    SmallVector<char, 10> string(std::begin(kString), std::end(kString));
+
+    EXPECT_FALSE(string.dynamic());
+    EXPECT_STREQ(string.begin(), "123456");
+    EXPECT_EQ(string.size(), 7u);
+
+    // Similar to emplace, but replaces rather than inserts.
+    string.replace(string.begin() + 5, '\0');
+    EXPECT_STREQ(string.begin(), "12345");
+
+    swap(chars, string);
+
+    EXPECT_STREQ(chars.begin(), "12345");
+    EXPECT_STREQ(string.begin(), "abcdefghij");
+
+    EXPECT_FALSE(chars.dynamic());
+    EXPECT_TRUE(string.dynamic());
+}
+
+TEST(SmallVector, CopyableElement) {
+    struct Pair {
+        // Needed because std::vector emplace does not use uniform initialization.
+        Pair(int a, int b) : a(a), b(b) {}
+
+        const int a, b;
+        bool operator==(Pair p) const { return p.a == a && p.b == b; }
+    };
+
+    SmallVector<Pair, 5> pairs;
+
+    EXPECT_TRUE(pairs.empty());
+    EXPECT_EQ(pairs.max_size(), 5u);
+
+    for (size_t i = 0; i < pairs.max_size(); ++i) {
+        EXPECT_EQ(pairs.size(), i);
+
+        const int a = static_cast<int>(i) * 2;
+        EXPECT_EQ(pairs.emplace_back(a, a + 1), Pair(a, a + 1));
+    }
+
+    EXPECT_EQ(pairs.size(), 5u);
+    EXPECT_FALSE(pairs.dynamic());
+
+    // The vector is promoted when full.
+    EXPECT_EQ(pairs.emplace_back(10, 11), Pair(10, 11));
+    EXPECT_TRUE(pairs.dynamic());
+
+    EXPECT_EQ(pairs,
+              (SmallVector{Pair{0, 1}, Pair{2, 3}, Pair{4, 5}, Pair{6, 7}, Pair{8, 9},
+                           Pair{10, 11}}));
+
+    // Constructor takes at most N elements.
+    SmallVector<int, 6> sums = {0, 0, 0, 0, 0, 0};
+    EXPECT_FALSE(sums.dynamic());
+
+    // Random-access iterators comply with standard.
+    std::transform(pairs.begin(), pairs.end(), sums.begin(), [](Pair p) { return p.a + p.b; });
+    EXPECT_EQ(sums, (SmallVector{1, 5, 9, 13, 17, 21}));
+
+    sums.pop_back();
+    std::reverse(sums.begin(), sums.end());
+
+    EXPECT_EQ(sums, (SmallVector{17, 13, 9, 5, 1}));
+}
+
+TEST(SmallVector, MovableElement) {
+    // Construct std::string elements in-place from C-style strings. Without std::in_place_type, the
+    // element type would be deduced from the first element, i.e. const char*.
+    SmallVector strings(std::in_place_type<std::string>, "", "", "", "cake", "velvet", "red", "");
+    strings.pop_back();
+
+    EXPECT_EQ(strings.max_size(), 7u);
+    EXPECT_EQ(strings.size(), 6u);
+
+    // Erase "cake" and append a substring copy.
+    {
+        const auto it = std::find_if(strings.begin(), strings.end(),
+                                     [](const auto& s) { return !s.empty(); });
+        ASSERT_FALSE(it == strings.end());
+        EXPECT_EQ(*it, "cake");
+
+        strings.unstable_erase(it);
+        EXPECT_EQ(strings.emplace_back("cakewalk", 4u), "cake"s);
+    }
+
+    strings[1] = "quince"s;
+
+    // Replace last empty string with "tart".
+    {
+        const auto rit = std::find(strings.rbegin(), strings.rend(), std::string());
+        ASSERT_FALSE(rit == strings.rend());
+
+        std::initializer_list<char> list = {'t', 'a', 'r', 't'};
+        strings.replace(rit.base() - 1, list);
+    }
+
+    strings.front().assign("pie");
+
+    EXPECT_EQ(strings, (SmallVector{"pie"s, "quince"s, "tart"s, "red"s, "velvet"s, "cake"s}));
+
+    strings.push_back("nougat");
+    strings.push_back("oreo");
+    EXPECT_TRUE(strings.dynamic());
+
+    std::rotate(strings.begin(), strings.end() - 2, strings.end());
+
+    EXPECT_EQ(strings,
+              (SmallVector{"nougat"s, "oreo"s, "pie"s, "quince"s, "tart"s, "red"s, "velvet"s,
+                           "cake"s}));
+}
+
+TEST(SmallVector, Replace) {
+    // Replacing does not require a copy/move assignment operator.
+    struct Word {
+        explicit Word(std::string str) : str(std::move(str)) {}
+        const std::string str;
+
+        bool operator==(const Word& other) const { return other.str == str; }
+    };
+
+    SmallVector words(std::in_place_type<Word>, "colored", "velour");
+
+    // The replaced element can be referenced by the replacement.
+    {
+        const Word& word = words.replace(words.last(), words.back().str.substr(0, 3) + "vet");
+        EXPECT_EQ(word, Word("velvet"));
+    }
+
+    // The vector is not promoted if replacing while full.
+    EXPECT_FALSE(words.dynamic());
+
+    words.emplace_back("cake");
+    EXPECT_TRUE(words.dynamic());
+
+    {
+        const Word& word = words.replace(words.begin(), words.front().str.substr(4));
+        EXPECT_EQ(word, Word("red"));
+    }
+
+    EXPECT_EQ(words, (SmallVector{Word("red"), Word("velvet"), Word("cake")}));
+}
+
+TEST(SmallVector, ReverseAppend) {
+    SmallVector strings = {"red"s, "velvet"s, "cake"s};
+    EXPECT_FALSE(strings.dynamic());
+
+    auto rit = strings.rbegin();
+    while (rit != strings.rend()) {
+        // Iterator and reference are invalidated on insertion.
+        const auto i = std::distance(strings.begin(), rit.base());
+        std::string s = *rit;
+
+        strings.push_back(std::move(s));
+        rit = std::make_reverse_iterator(strings.begin() + i) + 1;
+    }
+
+    EXPECT_EQ(strings, (SmallVector{"red"s, "velvet"s, "cake"s, "cake"s, "velvet"s, "red"s}));
+    EXPECT_TRUE(strings.dynamic());
+}
+
+TEST(SmallVector, Sort) {
+    SmallVector strings(std::in_place_type<std::string>, "pie", "quince", "tart", "red", "velvet");
+    strings.push_back("cake"s);
+
+    auto sorted = std::move(strings);
+    EXPECT_TRUE(strings.empty());
+
+    EXPECT_TRUE(sorted.dynamic());
+    EXPECT_TRUE(strings.dynamic());
+
+    std::sort(sorted.begin(), sorted.end());
+    EXPECT_EQ(sorted, (SmallVector{"cake"s, "pie"s, "quince"s, "red"s, "tart"s, "velvet"s}));
+
+    // Constructor takes array reference.
+    {
+        const char* kStrings[] = {"cake", "lie"};
+        strings = SmallVector(kStrings);
+        EXPECT_FALSE(strings.dynamic());
+    }
+
+    EXPECT_GT(sorted, strings);
+    swap(sorted, strings);
+    EXPECT_LT(sorted, strings);
+
+    EXPECT_FALSE(sorted.dynamic());
+    EXPECT_TRUE(strings.dynamic());
+
+    // Append remaining elements, such that "pie" is the only difference.
+    for (const char* str : {"quince", "red", "tart", "velvet"}) {
+        sorted.emplace_back(str);
+    }
+    EXPECT_TRUE(sorted.dynamic());
+
+    EXPECT_NE(sorted, strings);
+
+    // Replace second element with "pie".
+    const auto it = sorted.begin() + 1;
+    EXPECT_EQ(sorted.replace(it, 'p' + it->substr(1)), "pie");
+
+    EXPECT_EQ(sorted, strings);
+}
+
+namespace {
+
+struct DestroyCounts {
+    DestroyCounts(int& live, int& dead) : counts{live, dead} {}
+    DestroyCounts(const DestroyCounts& other) : counts(other.counts) {}
+    DestroyCounts(DestroyCounts&& other) : counts(other.counts) { other.alive = false; }
+    ~DestroyCounts() { ++(alive ? counts.live : counts.dead); }
+
+    struct {
+        int& live;
+        int& dead;
+    } counts;
+
+    bool alive = true;
+};
+
+void swap(DestroyCounts& lhs, DestroyCounts& rhs) {
+    std::swap(lhs.alive, rhs.alive);
+}
+
+} // namespace
+
+TEST(SmallVector, Destroy) {
+    int live = 0;
+    int dead = 0;
+
+    { SmallVector<DestroyCounts, 3> counts; }
+    EXPECT_EQ(0, live);
+    EXPECT_EQ(0, dead);
+
+    {
+        SmallVector<DestroyCounts, 3> counts;
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+
+        EXPECT_FALSE(counts.dynamic());
+    }
+    EXPECT_EQ(3, live);
+    EXPECT_EQ(0, dead);
+
+    live = 0;
+    {
+        SmallVector<DestroyCounts, 3> counts;
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+
+        EXPECT_TRUE(counts.dynamic());
+    }
+    EXPECT_EQ(4, live);
+    EXPECT_EQ(3, dead);
+
+    live = dead = 0;
+    {
+        SmallVector<DestroyCounts, 2> counts;
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+
+        auto copy = counts;
+        EXPECT_TRUE(copy.dynamic());
+    }
+    EXPECT_EQ(6, live);
+    EXPECT_EQ(2, dead);
+
+    live = dead = 0;
+    {
+        SmallVector<DestroyCounts, 2> counts;
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+        counts.emplace_back(live, dead);
+
+        auto move = std::move(counts);
+        EXPECT_TRUE(move.dynamic());
+    }
+    EXPECT_EQ(3, live);
+    EXPECT_EQ(2, dead);
+
+    live = dead = 0;
+    {
+        SmallVector<DestroyCounts, 2> counts1;
+        counts1.emplace_back(live, dead);
+        counts1.emplace_back(live, dead);
+        counts1.emplace_back(live, dead);
+
+        EXPECT_TRUE(counts1.dynamic());
+        EXPECT_EQ(2, dead);
+        dead = 0;
+
+        SmallVector<DestroyCounts, 2> counts2;
+        counts2.emplace_back(live, dead);
+
+        EXPECT_FALSE(counts2.dynamic());
+
+        swap(counts1, counts2);
+
+        EXPECT_FALSE(counts1.dynamic());
+        EXPECT_TRUE(counts2.dynamic());
+
+        EXPECT_EQ(0, live);
+        EXPECT_EQ(1, dead);
+
+        dead = 0;
+    }
+    EXPECT_EQ(4, live);
+    EXPECT_EQ(0, dead);
+}
+
+} // namespace android::test