Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use std::vector for std::array initialization

Suppose I have a std::vector of a size known at compile time, and I want to turn that into an std::array. How would I do that? Is there a standard function to do this?

The best solution I have so far is this:

template<class T, std::size_t N, class Indexable, std::size_t... Indices>
std::array<T, N> to_array_1(const Indexable& indexable, std::index_sequence<Indices...>) {
  return {{ indexable[Indices]... }};
}

template<class T, std::size_t N, class Indexable>
std::array<T, N> to_array(const Indexable& indexable) {
  return to_array_1<T, N>(indexable, std::make_index_sequence<N>());
}

std::array<Foo, 123> initFoos {
  std::vector<Foo> lst;
  for (unsigned i = 0; i < 123; ++i)
    lst.push_back(getNextFoo(lst));
  return to_array<Foo, 123>(lst); // passing lst.begin() works, too
}

The application is similar to Populate std::array with non-default-constructible type (no variadic templates): I too have a type which is not default-constructible, so I need to compute the actual values by the time the array gets initialized. However contrary to that question, for me the values are not merely a function of the index, but also of the preceding values. I can build my values much more easily using a loop than a series of function calls. So I construct the elements in a loop and place them in a vector, then I want to use the final state of that vector to initialize the array.

The above seems to compile and work fine, but perhaps there are ways to improve it.

  1. Perhaps I could make clever use of some standard library functionality that I wasn't aware of.
  2. Perhaps I can avoid the helper function somehow.
  3. Perhaps I can somehow formulate this in such a way that it works with move semantics for the elements instead of the copy semantics employed above.
  4. Perhaps I can avoid the random access using operator[], and instead use forward iterator semantics only so it would work for std::set or std::forward_list as input, too.
  5. Perhaps I should stop using std::vector and instead express my goal using std::array<std::optional<T>, N> instead, using C++17 or some equivalent implementation.

Related questions:

  • Copy std::vector into std::array which doesn't assume a type without default constructor, so copying after default initialization is feasible there but not for me.
  • Populate std::array with non-default-constructible type (no variadic templates) which computes each element from its index independently, so it tries to avoid an intermediate container. Answers use variadic templates even though the title requests avoiding them.
like image 296
MvG Avatar asked Dec 02 '16 10:12

MvG


People also ask

Can I use vector as array?

You can access a vector as an array.

Can we use vector as array in C++?

Therefore, array of vectors is two dimensional array with fixed number of rows where each row is vector of variable length. Each index of array stores a vector which can be traversed and accessed using iterators. Insertion: Insertion in array of vectors is done using push_back() function.

Does std :: array initialize?

std::array contains a built-in array, which can be initialized via an initializer list, which is what the inner set is. The outer set is for aggregate initialization.


Video Answer


1 Answers

I would propose:

template<typename T, typename Iter, std::size_t... Is>
constexpr auto to_array(Iter& iter, std::index_sequence<Is...>)
-> std::array<T, sizeof...(Is)> {
    return {{ ((void)Is, *iter++)... }};
}

template<std::size_t N, typename Iter,
         typename T = typename std::iterator_traits<Iter>::value_type>
constexpr auto to_array(Iter iter)
-> std::array<T, N> {
    return to_array<T>(iter, std::make_index_sequence<N>{});
}

This deduces the element type from the iterator and leaves copy-vs-move semantics up to the caller – if the caller wants to move, they can opt-in via std::move_iterator or the like:

auto initFoos() {
    constexpr unsigned n{123};

    std::vector<Foo> lst;
    for (unsigned i{}; i != n; ++i) {
        lst.push_back(getNextFoo(lst));
    }

    // copy-init array elements
    return to_array<n>(lst.cbegin());

    // move-init array elements
    return to_array<n>(std::make_move_iterator(lst.begin()));
}

Online Demo


EDIT: If one wants to override the deduced element type, as indicated in the comments, then I propose:

template<typename T, typename Iter, std::size_t... Is>
constexpr auto to_array(Iter& iter, std::index_sequence<Is...>)
-> std::array<T, sizeof...(Is)> {
    return {{ ((void)Is, T(*iter++))... }};
}

template<std::size_t N, typename U = void, typename Iter,
         typename V = typename std::iterator_traits<Iter>::value_type,
         typename T = std::conditional_t<std::is_same<U, void>{}, V, U>>
constexpr auto to_array(Iter iter)
-> std::array<T, N> {
    return to_array<T>(iter, std::make_index_sequence<N>{});
}

This leaves the element type optional but makes it the second parameter rather than the first, so usage would look like to_array<N, Bar>(lst.begin()) rather than to_array<Bar, N>(lst.begin()).

like image 132
ildjarn Avatar answered Sep 23 '22 13:09

ildjarn