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

Commit ccd50a4f authored by Dominik Laskowski's avatar Dominik Laskowski
Browse files

FTL: Generalize StaticVector in-place construction

Replace the std::in_place_type constructor, which is limited to one
argument per element, with construction from InitializerList, which
forwards argument tuples for each element.

SmallMap will use this syntax for its key-value pair constructor.

Bug: 160012986
Test: ftl_test
Change-Id: I9331f20268532cb09e980475d68768ba891aca1f
parent 92f1eedc
Loading
Loading
Loading
Loading
+8 −2
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@
#include <algorithm>
#include <iterator>
#include <new>
#include <type_traits>

#define FTL_ARRAY_TRAIT(T, U) using U = typename ArrayTraits<T>::U

@@ -40,12 +41,17 @@ struct ArrayTraits {
    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));
        if constexpr (std::is_constructible_v<value_type, Args...>) {
            // TODO: Replace with std::construct_at in C++20.
            return new (ptr) value_type(std::forward<Args>(args)...);
        } else {
            // Fall back to list initialization.
            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.
+69 −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 <tuple>
#include <utility>

namespace android::ftl {

// Compile-time counterpart of std::initializer_list<T> that stores per-element constructor
// arguments with heterogeneous types. For a container with elements of type T, given Sizes
// (S0, S1, ..., SN), N elements are initialized: the first element is initialized with the
// first S0 arguments, the second element is initialized with the next S1 arguments, and so
// on. The list of Types (T0, ..., TM) is flattened, so M is equal to the sum of the Sizes.
//
// The InitializerList is created using ftl::init::list, and is consumed by constructors of
// containers. The function call operator is overloaded such that arguments are accumulated
// in a tuple with each successive call. For instance, the following calls initialize three
// strings using different constructors, i.e. string literal, default, and count/character:
//
//     ... = ftl::init::list<std::string>("abc")()(3u, '?');
//
// WARNING: The InitializerList returned by an ftl::init::list expression must be consumed
// immediately, since temporary arguments are destroyed after the full expression. Storing
// an InitializerList results in dangling references.
//
template <typename T, typename Sizes = std::index_sequence<>, typename... Types>
struct InitializerList;

template <typename T, size_t... Sizes, typename... Types>
struct InitializerList<T, std::index_sequence<Sizes...>, Types...> {
    // Creates a superset InitializerList by appending the number of arguments to Sizes, and
    // expanding Types with forwarding references for each argument.
    template <typename... Args>
    [[nodiscard]] constexpr auto operator()(Args&&... args) && -> InitializerList<
            T, std::index_sequence<Sizes..., sizeof...(Args)>, Types..., Args&&...> {
        return {std::tuple_cat(std::move(tuple),
                               std::forward_as_tuple(std::forward<Args>(args)...))};
    }

    // The temporary InitializerList returned by operator() is bound to an rvalue reference in
    // container constructors, which extends the lifetime of any temporary arguments that this
    // tuple refers to until the completion of the full expression containing the construction.
    std::tuple<Types...> tuple;
};

namespace init {

template <typename T, typename... Args>
[[nodiscard]] constexpr auto list(Args&&... args) {
    return InitializerList<T>{}(std::forward<Args>(args)...);
}

} // namespace init
} // namespace android::ftl
+13 −2
Original line number Diff line number Diff line
@@ -64,6 +64,16 @@ struct IsSmallVector;
//     assert(vector == (ftl::SmallVector{'h', 'i', '\0'}));
//     assert(!vector.dynamic());
//
//     ftl::SmallVector strings = ftl::init::list<std::string>("abc")
//                                                            ("123456", 3u)
//                                                            (3u, '?');
//     assert(strings.size() == 3u);
//     assert(!strings.dynamic());
//
//     assert(strings[0] == "abc");
//     assert(strings[1] == "123");
//     assert(strings[2] == "???");
//
template <typename T, size_t N>
class SmallVector final : ArrayTraits<T>, ArrayComparators<SmallVector> {
    using Static = StaticVector<T, N>;
@@ -365,8 +375,9 @@ template <typename T, typename... Us, typename V = std::decay_t<T>,
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)>;
template <typename T, size_t... Sizes, typename... Types>
SmallVector(InitializerList<T, std::index_sequence<Sizes...>, Types...>&&)
        -> SmallVector<T, sizeof...(Sizes)>;

// Deduction guide for StaticVector conversion.
template <typename T, size_t N>
+52 −9
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#pragma once

#include <ftl/ArrayTraits.h>
#include <ftl/InitializerList.h>

#include <algorithm>
#include <cassert>
@@ -32,7 +33,7 @@ constexpr struct IteratorRangeTag {} IteratorRange;
// Fixed-capacity, statically allocated counterpart of std::vector. Akin to 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
// 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.
@@ -63,6 +64,14 @@ constexpr struct IteratorRangeTag {} IteratorRange;
//     vector = ftl::StaticVector(array);
//     assert(vector == (ftl::StaticVector{'h', 'i', '\0'}));
//
//     ftl::StaticVector strings = ftl::init::list<std::string>("abc")
//                                                             ("123456", 3u)
//                                                             (3u, '?');
//     assert(strings.size() == 3u);
//     assert(strings[0] == "abc");
//     assert(strings[1] == "123");
//     assert(strings[2] == "???");
//
template <typename T, size_t N>
class StaticVector final : ArrayTraits<T>,
                           ArrayIterators<StaticVector<T, N>, T>,
@@ -153,15 +162,22 @@ public:
        static_assert(sizeof...(elements) < N, "Too many elements");
    }

    // Constructs at most N elements. The template arguments T and N are inferred using the
    // deduction guide defined below. Element types must be convertible to the specified T:
    // 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<std::string>("abc")()(3u, '?');
    //
    //     ftl::StaticVector vector(std::in_place_type<std::string>, "red", "velvet", "cake");
    //     static_assert(std::is_same_v<decltype(vector), ftl::StaticVector<std::string, 3>>);
    //     assert(vector.full());
    //     assert(vector[0] == "abc");
    //     assert(vector[1].empty());
    //     assert(vector[2] == "???");
    //
    template <typename... Es>
    explicit StaticVector(std::in_place_type_t<T>, Es... elements)
          : StaticVector(std::forward<Es>(elements)...) {}
    template <typename U, size_t Size, size_t... Sizes, typename... Types>
    StaticVector(InitializerList<U, std::index_sequence<Size, Sizes...>, Types...>&& init)
          : StaticVector(std::index_sequence<0, 0, Size>{}, std::make_index_sequence<Size>{},
                         std::index_sequence<Sizes...>{}, init.tuple) {}

    ~StaticVector() { std::destroy(begin(), end()); }

@@ -292,6 +308,32 @@ private:
    template <size_t I>
    explicit StaticVector(std::index_sequence<I>) : mSize(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 <size_t I, size_t ArgIndex, size_t ArgCount, size_t... Indices, size_t Size,
              size_t... Sizes, typename... Args>
    StaticVector(std::index_sequence<I, ArgIndex, ArgCount>, std::index_sequence<Indices...>,
                 std::index_sequence<Size, Sizes...>, std::tuple<Args...>& tuple)
          : StaticVector(std::index_sequence<I + 1, ArgIndex + ArgCount, Size>{},
                         std::make_index_sequence<Size>{}, std::index_sequence<Sizes...>{}, tuple) {
        construct_at(begin() + I, std::move(std::get<ArgIndex + Indices>(tuple))...);
    }

    // Base case for in-place constructor.
    template <size_t I, size_t ArgIndex, size_t ArgCount, size_t... Indices, typename... Args>
    StaticVector(std::index_sequence<I, ArgIndex, ArgCount>, std::index_sequence<Indices...>,
                 std::index_sequence<>, std::tuple<Args...>& tuple)
          : mSize(I + 1) {
        construct_at(begin() + I, std::move(std::get<ArgIndex + Indices>(tuple))...);
    }

    size_type mSize = 0;
    std::aligned_storage_t<sizeof(value_type), alignof(value_type)> mData[N];
};
@@ -306,8 +348,9 @@ template <typename T, typename... Us, typename V = std::decay_t<T>,
StaticVector(T&&, Us&&...) -> StaticVector<V, 1 + sizeof...(Us)>;

// Deduction guide for in-place constructor.
template <typename T, typename... Us>
StaticVector(std::in_place_type_t<T>, Us&&...) -> StaticVector<T, sizeof...(Us)>;
template <typename T, size_t... Sizes, typename... Types>
StaticVector(InitializerList<T, std::index_sequence<Sizes...>, Types...>&&)
        -> StaticVector<T, sizeof...(Sizes)>;

template <typename T, size_t N>
template <typename E>
+17 −7
Original line number Diff line number Diff line
@@ -52,6 +52,14 @@ TEST(SmallVector, Example) {
    vector = ftl::SmallVector(array);
    EXPECT_EQ(vector, (ftl::SmallVector{'h', 'i', '\0'}));
    EXPECT_FALSE(vector.dynamic());

    ftl::SmallVector strings = ftl::init::list<std::string>("abc")("123456", 3u)(3u, '?');
    ASSERT_EQ(strings.size(), 3u);
    EXPECT_FALSE(strings.dynamic());

    EXPECT_EQ(strings[0], "abc");
    EXPECT_EQ(strings[1], "123");
    EXPECT_EQ(strings[2], "???");
}

TEST(SmallVector, Construct) {
@@ -99,7 +107,8 @@ TEST(SmallVector, Construct) {
    }
    {
        // In-place constructor with same types.
        SmallVector vector(std::in_place_type<std::string>, "red", "velvet", "cake");
        SmallVector vector =
                ftl::init::list<std::string>("redolent", 3u)("velveteen", 6u)("cakewalk", 4u);

        static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 3>>);
        EXPECT_EQ(vector, (SmallVector{"red"s, "velvet"s, "cake"s}));
@@ -110,9 +119,10 @@ TEST(SmallVector, Construct) {
        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);
        SmallVector vector = ftl::init::list<std::string>(copy.c_str())(std::move(move))(list);

        static_assert(std::is_same_v<decltype(vector), SmallVector<std::string, 3>>);
        EXPECT_TRUE(move.empty());
        EXPECT_EQ(vector, (SmallVector{"red"s, "velvet"s, "cake"s}));
        EXPECT_FALSE(vector.dynamic());
    }
@@ -206,9 +216,8 @@ TEST(SmallVector, CopyableElement) {
}

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", "");
    // Construct std::string elements in place from per-element arguments.
    SmallVector strings = ftl::init::list<std::string>()()()("cake")("velvet")("red")();
    strings.pop_back();

    EXPECT_EQ(strings.max_size(), 7u);
@@ -221,6 +230,7 @@ TEST(SmallVector, MovableElement) {
        ASSERT_FALSE(it == strings.end());
        EXPECT_EQ(*it, "cake");

        // Construct std::string from first 4 characters of string literal.
        strings.unstable_erase(it);
        EXPECT_EQ(strings.emplace_back("cakewalk", 4u), "cake"s);
    }
@@ -260,7 +270,7 @@ TEST(SmallVector, Replace) {
        bool operator==(const Word& other) const { return other.str == str; }
    };

    SmallVector words(std::in_place_type<Word>, "colored", "velour");
    SmallVector words = ftl::init::list<Word>("colored")("velour");

    // The replaced element can be referenced by the replacement.
    {
@@ -301,7 +311,7 @@ TEST(SmallVector, ReverseAppend) {
}

TEST(SmallVector, Sort) {
    SmallVector strings(std::in_place_type<std::string>, "pie", "quince", "tart", "red", "velvet");
    SmallVector strings = ftl::init::list<std::string>("pie")("quince")("tart")("red")("velvet");
    strings.push_back("cake"s);

    auto sorted = std::move(strings);
Loading