I have been playing with auto
and I noticed that for most cases you can replace a variable definition with auto
and then assign the type.
In the following code w
and x
are equivalent (default initialized int
, but lets not get into potential copies). Is there a way to declare z
such that it has the same type as y
?
int w{};
auto x = int{};
int y[5];
auto z = int[5];
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.
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.
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);