Search code examples
c++templatesc++17fold-expression

How to correctly unfold a pack in order to create N element array


I was expecting this code to create 10 elements vector each constructed as A{1, 2, 3.0}, but in reality the output is 1

#include <iostream>
#include <vector>

struct A {
    int a{0};
    int b{0};
    double c{0.0};
};

template<typename T, size_t... Ns>
auto create_array(std::index_sequence<Ns...>, auto&&... args) {
    return std::vector<T> {
        ([&](...){ return std::forward<T>(T{args...}); }(Ns), ...)
    };
}

template<typename T, size_t N>
auto create_array(auto&&... args) {
    return create_array<T>(std::make_index_sequence<N>{}, std::forward<decltype(args)>(args)...);
}

int main() {
    std::cout << create_array<A, 10>(1,2,3.0).size() << std::endl;
}

How could this happen, why did not it unfold correctly? What is wrong here? In particular I wanted this line

([&](...){ return std::forward<T>(T{args...}); }(Ns), ...)

to become

[&](...){ return std::forward<T>(T{args...}); }(0),
[&](...){ return std::forward<T>(T{args...}); }(1),
[&](...){ return std::forward<T>(T{args...}); }(2),
//...

Solution

  • The line you mention:

    ([&](...){ return std::forward<T>(T{args...}); }(Ns), ...)
    

    is a fold expression. It only returns the last value because that's how the comma operator works. Remove the parentheses and the comma to instead expand the expression with Ns into an initializer list:

    template<typename T, size_t... Ns>
    auto create_array(std::index_sequence<Ns...>, auto&&... args) {
        return std::vector<T> {
            [&](...){ return std::forward<T>(T{args...}); }(Ns)...
        };
    }