Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Initialize std::array with a range (pair of iterators)

Tags:

How can I initialize an std::array from a range (as defined by a pair of iterators)?

Something like this:

vector<T> v; ... // I know v has exactly N elements (e.g. I just called v.resize(N)) // Now I want a initialized with those elements array<T, N> a(???);  // what to put here? 

I thought array would have a constructor taking a pair of iterators, so that I could do array<T, N> a(v.begin(), v.end()), but it appears to have no constructors at all!

I know I can copy the vector into the array, but I'd rather initialize the array with the vector contents directly, without default-constructing it first. How can I?

like image 383
HighCommander4 Avatar asked Jun 07 '12 09:06

HighCommander4


2 Answers

With random access iterators, and assuming a certain size at compile-time, you can use a pack of indices to do so:

template <std::size_t... Indices> struct indices {     using next = indices<Indices..., sizeof...(Indices)>; }; template <std::size_t N> struct build_indices {     using type = typename build_indices<N-1>::type::next; }; template <> struct build_indices<0> {     using type = indices<>; }; template <std::size_t N> using BuildIndices = typename build_indices<N>::type;  template <typename Iterator> using ValueType = typename std::iterator_traits<Iterator>::value_type;  // internal overload with indices tag template <std::size_t... I, typename RandomAccessIterator,           typename Array = std::array<ValueType<RandomAccessIterator>, sizeof...(I)>> Array make_array(RandomAccessIterator first, indices<I...>) {     return Array { { first[I]... } }; }  // externally visible interface template <std::size_t N, typename RandomAccessIterator> std::array<ValueType<RandomAccessIterator>, N> make_array(RandomAccessIterator first, RandomAccessIterator last) {     // last is not relevant if we're assuming the size is N     // I'll assert it is correct anyway     assert(last - first == N);      return make_array(first, BuildIndices<N> {}); }  // usage auto a = make_array<N>(v.begin(), v.end()); 

This assumes a compiler capable of eliding the intermediate copies. I think that assumption is not a big stretch.

Actually, it can be done with input iterators as well, since the computation of each element in a braced-init-list is sequenced before the computation of the next element (§8.5.4/4).

// internal overload with indices tag template <std::size_t... I, typename InputIterator,           typename Array = std::array<ValueType<InputIterator>, sizeof...(I)>> Array make_array(InputIterator first, indices<I...>) {     return Array { { (void(I), *first++)... } }; }     

Since *first++ doesn't have any I in it, we need a dummy I to provoke the pack expansion. Comma operator to the rescue, with void() to silence warnings about lack of effects, and also preventing overloaded commas.

like image 137
R. Martinho Fernandes Avatar answered Oct 17 '22 06:10

R. Martinho Fernandes


Like you have noticed, std::array has no constructors at all (except for the compiler generated default constructor).

This was done on purpose, so it can be statically initialized just like a C array. If you want to fill the array without a static initializer, you will have to copy your data.

like image 42
Bo Persson Avatar answered Oct 17 '22 05:10

Bo Persson