Initialize Std::Array with a Range (Pair of Iterators)

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

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.

On the initialization of std::array

No.

std::array is an aggregate, so you get no special functionality like constructors taking iterators. (This actually surprises me, with the introduction of std::initializer_list I see no harm in making other useful constructors. Perhaps a question is in store.)

This means the only way to use iterators to copy data inside the array is to iterate, and to do that the array must be already constructed and ready to use.

Initializing std::vector with ranges library

What you’re looking for is

auto b=std::ranges::to<std::vector>(std::ranges::iota_view(0, 5));

Unfortunately, that proposal missed C++20 simply because there wasn’t time to review its wording (after a previous version that added the constructor you tried was found unworkable). Hopefully it’ll be merged—and implemented—early in the C++23 cycle.

How to initialize std::vector from C-style array?

Don't forget that you can treat pointers as iterators:

w_.assign(w, w + len);

Iterating over a part of an array in a range-based loop

Simply adapt the range to be a different type where begin() and end() do the right thing.

struct Slice {
int* arr;
size_t n;
int* begin() { return arr; }
int* end() { return arr + n; }
};

for(auto elem : Slice{someArray, 100}) {/*doStuff*/}

How to initialize std::map by an array?

How about this?

const char* s[] = { "car", "sun", "surprise", "asteriks", "alpha", "apple" };
std::map<char, std::set<std::string>> myMap;

for (auto str : s)
myMap[str[0]].insert(str);

// Result: {
// 'a' -> {"alpha", "apple", "asteriks"},
// 'c' -> {"car"},
// 's' -> {"sun", "surprise"}
// }

Range-based for with brace-initializer over non-const values?

You are guessing correctly. std::initializer_list elements are always const (which makes sort()ing them impossible, as sort() is a non-const member function) and its elements are always copied (which would make sort()-ing them meaningless even if they weren't const). From [dcl.init.list], emphasis mine:

An object of type std::initializer_list<E> is constructed from an initializer list as if the implementation
allocated a temporary array of N elements of type const E, where N is the number of elements in the
initializer list. Each element of that array is copy-initialized with the corresponding element of the initializer
list, and the std::initializer_list<E> object is constructed to refer to that array. [ Note: A constructor
or conversion function selected for the copy shall be accessible (Clause 11) in the context of the initializer
list. —end note ] If a narrowing conversion is required to initialize any of the elements, the program is
ill-formed. [ Example:

struct X {
X(std::initializer_list<double> v);
};
X x{ 1,2,3 };

The initialization will be implemented in a way roughly equivalent to this:

const double __a[3] = {double{1}, double{2}, double{3}};
X x(std::initializer_list<double>(__a, __a+3));

assuming that the implementation can construct an initializer_list object with a pair of pointers. —end
example ]

There is no way to make them non-const or non-copied. The pointer solution works:

for (auto l : {&a, &b, &c}) l->sort();

because it's the pointer that's const, not the element it's pointing to. The other alternative would be to write a variadic function template:

template <typename... Lists>
void sortAll(Lists&&... lists) {
// before C++17
using expander = int[];
expander{0, (void(lists.sort()), 0)...};

// C++17 or later
(lists.sort(), ...);
}

sortAll(a, b, c);

You could also, I guess, write a helper to wrap your lists into an array of reference_wrapper to list<int> (since you can't have an array of references), but this is probably more confusing than helpful:

template <typename List, typename... Lists>
std::array<std::reference_wrapper<List>, sizeof...(Lists) + 1>
as_array(List& x, Lists&... xs) {
return {x, xs...};
}

for (list<int>& l : as_array(a, b, c)) { // can't use auto, that deduces
l.sort(); // reference_wrapper<list<int>>,
} // so would need l.get().sort()

Is one-line initialization of a <set> possible with C++20 <ranges>?

Actually, it is not possible for now, but in near future, we can get ranges::to which will have such functionality.



Related Topics



Leave a reply



Submit