TL;DR
template<typename T, int N> using raw_array = T[N];
auto &&z = raw_array<int,5>{};
Your example of auto z = int[5];
isn't legal any more than auto z = int;
is, simply because a type is not a valid initializer. You can write: auto z = int{};
because int{}
is a valid initializer.
Once one realizes this, the next attempt would be:
auto z = int[5]{};
Note that your int y[5]
does not have any initializer. If it had then you would have jumped straight here.
Unfortunately this does not work either for obscure syntax reasons. Instead you must find a legal way to name the array type in an initializer. For example, a typedef name can be used in an initializer. A handy reusable template type alias eliminates the burdensome requirement of a new typedef for every array type:
template<typename T, int N> using raw_array = T[N];
auto z = raw_array<int,5>{};
Aside: You can use template type aliases to fix the weird 'inside-out' syntax of C++, allowing you to name any compound type in an orderly, left-to-right fashion, by using this proposal.
Unfortunately due to the design bug in C and C++ which causes array-to-pointer conversions at the drop of a hat, the deduced type of the variable z
is int*
rather int[5]
. The resulting variable becomes a dangling pointer when the temporary array is destroyed.
C++14 introduces decltype(auto)
which uses different type deduction rules, correctly deducing an array type:
decltype(auto) z = raw_array<int,5>{};
But now we run into another design bug with arrays; they do not behave as proper objects. You can't assign, copy construct, do pass by value, etc., with arrays. The above code is like saying:
int g[5] = {};
int h[5] = g;
By all rights this should work, but unfortunately built-in arrays behave bizarrely in C and C++. In our case, the specific problem is that arrays are not allowed to have just any kind of initializer; they are strictly limited to using initializer lists. An array temporary, initialized by an initializer list, is not itself an initializer list.
Answer 1:
At this point Johannes Schaub makes the excellent suggestion that we can use temporary lifetime extension.
auto &&z = raw_array<int,5>{};
decltype(auto)
isn't needed because the addition of &&
changes the deduced type, so Johannes Schaub's suggestion works in C++11. This also avoids the limitation on array initializers because we're initializing a reference instead of an array.
If you want the array to deduce its length from an initializer, you can use an incomplete array type:
template<typename T> using unsized_raw_array = T[];
auto &&z = unsized_raw_array<int>{1, 2, 3};
Although the above does what you want you may prefer to avoid raw arrays entirely, due to the fact that raw arrays do not behave like proper C++ objects, and the obscurity of their behavior and the techniques used above.
Answer 2:
The std::array
template in C++11 does act like a proper object, including assignment, being passable by value, etc., and just generally behaving sanely and consistently where built-in arrays do not.
auto z = std::array<int,5>{};
However, with this you miss out on being able to have the array type infer its own length from an initializer. Instead You can write a make_array
template function that does the inference. Here's a really simple version I haven't tested and which doesn't do things you might want, such as verify that all the arguments are the same type, or let you explicitly specify the type.
template<typename... T>
std::array<typename std::common_type<T...>::type, sizeof...(T)>
make_array(T &&...t) {
return {std::forward<T>(t)...};
}
auto z = make_array(1,2,3,4,5);