Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0bacf27f authored by Dominik Laskowski's avatar Dominik Laskowski
Browse files

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
parent 0357237c
Loading
Loading
Loading
Loading
+129 −0
Original line number Diff line number Diff line
/*
 * 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
+381 −0
Original line number Diff line number Diff line
/*
 * 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
+41 −73
Original line number Diff line number Diff line
@@ -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 @@ constexpr struct IteratorRangeTag {} IteratorRange;
//     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 @@ class StaticVector final {
            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 @@ public:
    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 @@ public:
    //
    // 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 @@ public:
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 @@ private:
    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 @@ inline void swap(StaticVector<T, N>& lhs, StaticVector<T, N>& rhs) {
    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
+1 −0
Original line number Diff line number Diff line
@@ -5,6 +5,7 @@ cc_test {
        address: true,
    },
    srcs: [
        "SmallVector_test.cpp",
        "StaticVector_test.cpp",
    ],
    cflags: [
+455 −0

File added.

Preview size limit exceeded, changes collapsed.