23

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?

HighCommander4
  • 50,428
  • 24
  • 122
  • 194
  • Is there some reason for that preference? The performance will be almost precisely the same because the default constructor (typically) only allocates the base structures you need anyway. There would be no extra allocating, copying, or freeing. – David Schwartz Jun 07 '12 at 09:57
  • 1
    @DavidSchwartz: Perhaps I have a const array member in my class and so I need to initialize it in the initializer list rather than the constructor body? – HighCommander4 Jun 07 '12 at 10:03
  • ---Can we limit ourselves to random access iterators? If so, I have some kind of solution--- Nevermind, there is no way to get the *size* at compile time. – R. Martinho Fernandes Jun 07 '12 at 10:22
  • 1
    @R.MartinhoFernandes: You can **assume** the size is N at compile time - the same N that appears as a template parameter of `array`. – HighCommander4 Jun 07 '12 at 10:26
  • Actually, it can be done with input iterators! – R. Martinho Fernandes Jun 07 '12 at 11:20

3 Answers3

25

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.

R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
  • 1
    +1. Nice. (but this time I hate this syntax : `template>>>>`. I mean WTF.) – Nawaz Jun 07 '12 at 12:18
  • To clarify, `BuildIndices` is the `build_indices` template on the linked page? Could you include it here to make this answer standalone? – ecatmur Jun 07 '12 at 12:22
  • See my solution which uses Boost. – Nawaz Jun 07 '12 at 12:33
  • @ecatmur yeah, something like that. I didn't want to include it here, because this is not at all the first time I use this trick in an answer. I think I'll just link to a previous answer instead. – R. Martinho Fernandes Jun 07 '12 at 13:49
  • Btw, I love how the general C++11 feature usage in SO answers corresponds to what the latest version of GCC supports, e.g. a few months ago no one posted answers that use template aliases unless the question was about them :) – HighCommander4 Jun 08 '12 at 02:26
  • 2
    @HighCommander4 FWIW, clang supports them too :) I think alias templates are highly underrated. I started using them some months ago and I can't live without them already :) – R. Martinho Fernandes Jun 08 '12 at 02:32
  • Unfortunately, the input iterator version gives a crapload of "left operand of comma operator has no effect" warnings with GCC. Any ideas as to what I can do about that? – HighCommander4 Jun 08 '12 at 06:34
  • 1
    @HighCommander4 Ah, that's simple. If you convert it to `void`, you're telling GCC that having no-effects is what you intend. I updated my answer. – R. Martinho Fernandes Jun 08 '12 at 10:36
6

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.

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
5

You can use BOOST_PP_ENUM as:

include <boost/preprocessor/repetition/enum.hpp>

#define INIT(z, i, v) v[i] 

std::vector<int> v;

//fill v with at least 5 items 

std::array<int,5> a = { BOOST_PP_ENUM(5, INIT, v) };  //MAGIC

Here, the last line is expanded as:

std::array<int,5> a = {v[0], v[1], v[2], v[3], v[4]}; //EXPANDED LINE

which is what you want.

Nawaz
  • 353,942
  • 115
  • 666
  • 851