24

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?

5
  • 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. Commented Jun 7, 2012 at 9: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? Commented Jun 7, 2012 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. Commented Jun 7, 2012 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. Commented Jun 7, 2012 at 10:26
  • Actually, it can be done with input iterators! Commented Jun 7, 2012 at 11:20

3 Answers 3

27

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.

Sign up to request clarification or add additional context in comments.

8 Comments

+1. Nice. (but this time I hate this syntax : template<typename<Xyz<template<typename<Abc...>>>>>. I mean WTF.)
To clarify, BuildIndices is the build_indices template on the linked page? Could you include it here to make this answer standalone?
See my solution which uses Boost.
@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 :)
@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.
|
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.

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.