27

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];
sudo make install
  • 5,629
  • 3
  • 36
  • 48
Graznarak
  • 3,626
  • 4
  • 28
  • 47
  • 4
    The potential copies are important. You can't do this because you can't copy arrays. – David Brown Jun 05 '13 at 20:17
  • 3
    Not "default initialized int", but "zero-initialized int" – Andy Prowl Jun 05 '13 at 20:20
  • Apparently, there is a problem creating a temporary array, and then copying it, as @DavidBrown intimated. Even using a `typedef` for an array type fails, because of this. – jxh Jun 05 '13 at 20:44
  • Implicit default constructor for an int does zero-initialize, unlike an unitialized int when has whatever garbage was already at the location. – Graznarak Jun 05 '13 at 20:46
  • @Graznarak: Except it isn't an "implicit default constructor". Zero-filling of `int` (for example) occurs for value initialization and static initialization, but *not* default initialization. – Ben Voigt Jun 06 '13 at 15:30

5 Answers5

36

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);
Community
  • 1
  • 1
bames53
  • 86,085
  • 15
  • 179
  • 244
  • But I thought he wanted it to be uninitialized like `y`, or default initialized like `int{}`. Are you saying `int[5]{}` would work? – jxh Jun 05 '13 at 22:23
  • @jxh Yes, you can have a default initializer: `auto z = raw_array{};` – bames53 Jun 05 '13 at 22:27
  • @jxh that's a bug in gcc. However, I have discovered a problem that makes my answer incorrect. I'll have to update or delete my answer. – bames53 Jun 05 '13 at 22:34
  • Specifically, although `raw_array{}` does successfully produce an array temporary, the type deduced from it for the variable `z` is actually a pointer, pointing to the temporary. `z` then becomes a dangling pointer when the temporary is destroyed. – bames53 Jun 05 '13 at 22:36
  • That is what GCC is warning about: `taking address of temporary array`. – jxh Jun 05 '13 at 22:37
  • That array initialization however is useless as you cannot use the pointer variable afterwarxs. Unlike C's compound literals, such a functional cast creates a temporary array object and will leave you with a dangling pointer. – Johannes Schaub - litb Jun 05 '13 at 22:39
  • @jxh Ah, I see you're right. I remember at some poing gcc just didn't support this syntax. – bames53 Jun 05 '13 at 22:41
  • 3
    What you *can* do is using lifetime extension with references, i.e `auto&&`. – Johannes Schaub - litb Jun 05 '13 at 22:41
  • In `make_array`, shouldn't it be `T&&...` (`&& before the ellipsis)? And I think there's a `T, ` too much in the return type. But very exhaustive answer. – Christian Rau Jun 06 '13 at 09:04
10

Not quite the same, but you could use array:

auto z = std::array<int, 5>();
jxh
  • 69,070
  • 8
  • 110
  • 193
  • 5
    For giggles, we made a [make_array function](http://stackoverflow.com/q/6114067/596781) some time ago. – Kerrek SB Jun 05 '13 at 20:48
1

Better think about make_something from c++14

#include<iostream>
#include<experimental/array>
using namespace std;
using namespace std::experimental;
int main()
{
    auto arr = make_array(1,2,3);
    cout << arr.front() << endl;
    return 0;
}
LE SANG
  • 10,955
  • 7
  • 59
  • 78
0

decltype works with g++ 4.9.0 20130601 for this:

#include <iostream>
#include <algorithm>

static std::ostream& logger = std::clog;

class A {
    static int _counter;
    int _id;
  public:
    A() : _id(++_counter) {
        logger << "\tA #" << _id << " c'tored\n";
    } 

    ~A() {
        //logger << "\tA #" << _id << " d'tor\n";
    } 

    inline int id() const{
        return _id;
    }
};
int A::_counter(0); 

std::ostream& operator<<(std::ostream& os, const A& a) {
    return os << a.id();
}

int main() {

    auto dump = [](const A& a){ logger << a << " ";};

    logger << "x init\n";
    A x[5]; 
    logger << "x contains: "; std::for_each(x, x+5, dump);

    logger << "\ndecltype(x) y init\n";
    decltype(x) y;
    logger << "y contains: ";  std::for_each(y, y+5, dump);
    logger << std::endl;

    return 0;
}

Output:

x init
    A #1 c'tored
    A #2 c'tored
    A #3 c'tored
    A #4 c'tored
    A #5 c'tored
x contains: 1 2 3 4 5 
decltype(x) y init
    A #6 c'tored
    A #7 c'tored
    A #8 c'tored
    A #9 c'tored
    A #10 c'tored
y contains: 6 7 8 9 10 
Solkar
  • 1,228
  • 12
  • 22
0

Not exactly the same thing, and it's a bit ugly, but it is possible to deduce the element type from a list initializer and declare the array directly, as follows:

template<typename T>
struct array_trait
{
    using element_type = T;
    array_trait(T(&&)[]);
};

decltype(array_trait({4,5,7}))::element_type a[] = {4,5,7};

std::cout << typeid(a).name() << std::endl;

for (unsigned i = 0; i < 3; i++)
    std::cout << a[i] << std::endl;

The type should be int[3] and the output should be 4 5 7.

ThomasMcLeod
  • 7,603
  • 4
  • 42
  • 80