c++templatesindex-sequence

Pack expansion for std::array initialization using std::index_sequence


I need to initialize an std:array with N objects taking the same constructor arguments, as in std::vector<T>(size_t, {args...}). From my search here I came up with this, which works:

template<typename T, size_t... Ints, typename... Args>
std::array<T, sizeof...(Ints)> make_array_(std::index_sequence<Ints...>, Args&&... args) {
    return { (Ints, T{args...})... };
}

template<typename T, size_t N, typename... Args>
std::array<T, N> make_array(Args&&... args) {
    return make_array_<T>(std::make_index_sequence<N>(), args...);
}

struct AStruct {
    AStruct(int a, float b) : a_{ a }, b_{ b } {}
private:
    int a_; float b_;
}

int main() {
    auto anArray = make_array<AStruct, 10>(8, 5.4f);
    // etc...
}

But I don't understand the third line. How does (Ints, T{args...})... translate to T{args...}, T{args...}, T{args...}... N times?


Solution

  • The outer parameter pack is not expanded in the way you wrote, it is expanded like this (inner pack not shown expanded because you don't seem to have trouble with that):

    return { (0, T{args...}), (1, T{args...}), (2, T{args}) };
    

    where the integers are of type std::size_t.

    (0, T{args...}) is one element to the brace-enclosed initializer. It is an expression using the comma operator. The comma operator evaluates the left-hand side (0) first and then the right-hand side (T{args...}) second, returning the latter.

    Because evaluation of 0 has no side effect and its value is discarded by the comma operator, this is effectively equivalent to

    return { T{args...}, T{args...}, T{args...} };
    

    There is one caveat to this. The comma operator can be overloaded. If there is an overload accepting std::size_t as first and T as second argument, then this will have behaved in an unexpected manner, initializing the return value with the result of the overload instead. This can be prevented by casting the integer to void (which can never appear as argument to an overloaded operator call):

    return { (void(Ints), T{args...})... };