How to emulate C array initialization "int arr[] = { e1, e2, e3, ... }" behaviour with std::array? How to emulate C array initialization "int arr[] = { e1, e2, e3, ... }" behaviour with std::array? arrays arrays

How to emulate C array initialization "int arr[] = { e1, e2, e3, ... }" behaviour with std::array?


Best I can think of is:

template<class T, class... Tail>auto make_array(T head, Tail... tail) -> std::array<T, 1 + sizeof...(Tail)>{     std::array<T, 1 + sizeof...(Tail)> a = { head, tail ... };     return a;}auto a = make_array(1, 2, 3);

However, this requires the compiler to do NRVO, and then also skip the copy of returned value (which is also legal but not required). In practice, I would expect any C++ compiler to be able to optimize that such that it's as fast as direct initialization.


I'd expect a simple make_array.

template<typename ret, typename... T> std::array<ret, sizeof...(T)> make_array(T&&... refs) {    // return std::array<ret, sizeof...(T)>{ { std::forward<T>(refs)... } };    return { std::forward<T>(refs)... };}


Combining a few ideas from previous posts, here's a solution that works even for nested constructions (tested in GCC4.6):

template <typename T, typename ...Args>std::array<T, sizeof...(Args) + 1> make_array(T && t, Args &&... args){  static_assert(all_same<T, Args...>::value, "make_array() requires all arguments to be of the same type."); // edited in  return std::array<T, sizeof...(Args) + 1>{ std::forward<T>(t), std::forward<Args>(args)...};}

Strangely, can cannot make the return value an rvalue reference, that would not work for nested constructions. Anyway, here's a test:

auto q = make_array(make_array(make_array(std::string("Cat1"), std::string("Dog1")), make_array(std::string("Mouse1"), std::string("Rat1"))),                    make_array(make_array(std::string("Cat2"), std::string("Dog2")), make_array(std::string("Mouse2"), std::string("Rat2"))),                    make_array(make_array(std::string("Cat3"), std::string("Dog3")), make_array(std::string("Mouse3"), std::string("Rat3"))),                    make_array(make_array(std::string("Cat4"), std::string("Dog4")), make_array(std::string("Mouse4"), std::string("Rat4")))                    );std::cout << q << std::endl;// produces: [[[Cat1, Dog1], [Mouse1, Rat1]], [[Cat2, Dog2], [Mouse2, Rat2]], [[Cat3, Dog3], [Mouse3, Rat3]], [[Cat4, Dog4], [Mouse4, Rat4]]]

(For the last output I'm using my pretty-printer.)


Actually, let us improve the type safety of this construction. We definitely need all types to be the same. One way is to add a static assertion, which I've edited in above. The other way is to only enable make_array when the types are the same, like so:

template <typename T, typename ...Args>typename std::enable_if<all_same<T, Args...>::value, std::array<T, sizeof...(Args) + 1>>::typemake_array(T && t, Args &&... args){  return std::array<T, sizeof...(Args) + 1> { std::forward<T>(t), std::forward<Args>(args)...};}

Either way, you will need the variadic all_same<Args...> type trait. Here it is, generalizing from std::is_same<S, T> (note that decaying is important to allow mixing of T, T&, T const & etc.):

template <typename ...Args> struct all_same { static const bool value = false; };template <typename S, typename T, typename ...Args> struct all_same<S, T, Args...>{  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value && all_same<T, Args...>::value;};template <typename S, typename T> struct all_same<S, T>{  static const bool value = std::is_same<typename std::decay<S>::type, typename std::decay<T>::type>::value;};template <typename T> struct all_same<T> { static const bool value = true; };

Note that make_array() returns by copy-of-temporary, which the compiler (with sufficient optimisation flags!) is allowed to treat as an rvalue or otherwise optimize away, and std::array is an aggregate type, so the compiler is free to pick the best possible construction method.

Finally, note that you cannot avoid copy/move construction when make_array sets up the initializer. So std::array<Foo,2> x{Foo(1), Foo(2)}; has no copy/move, but auto x = make_array(Foo(1), Foo(2)); has two copy/moves as the arguments are forwarded to make_array. I don't think you can improve on that, because you can't pass a variadic initializer list lexically to the helper and deduce type and size -- if the preprocessor had a sizeof... function for variadic arguments, perhaps that could be done, but not within the core language.