Difference between std::vector and std::array init

2019-04-07 08:34发布

问题:

This C++11 code works fine for me:

#include <iostream>
#include <vector>
#include <array>
using namespace std;

struct str {
    int first, last;
};


vector<str> fields {
    {1,2}, {3,4}, {5,6}
};

int main()
{
    for (str s : fields)
        cout << s.first << " " << s.last << endl;
}

It prints the six expected values.

But if I change vector<str> to array<str,3>, gcc gives me this error: "too many initializers for ‘std::array’".

If I change the initialization of fields thus:

array<str,3> fields {
    str{1,2}, str{3,4}, str{5,6}
};

Things work nicely.

So why do I need str{1,2} when using std::array, but only {1,2} when using std::vector?

回答1:

See cppreference's section on aggregate initialization.

The effects of aggregate initialization are:

  • Each array element or non-static class member, in order of array subscript/appearance in the class definition, is copy-initialized from the corresponding clause of the initializer list.

  • If the initializer clause is a nested braced-init-list, the corresponding class member is itself an aggregate: aggregate initialization is recursive.

This means that if you had an aggregate inside your struct, such as:

struct str {
    struct asdf
    {
        int first, last;
    } asdf; 
};

asdf would be initialized by the first nested brace-init-list, i.e. { { 1, 2 } }. The reason why you generally need two pairs of braces is because the nested brace-init-list initializes the underlying aggregate in std::array (for example, T a[N]).

However, you can still initialize your array like this:

array<str,3> fields {
    1, 2, 3, 4, 5, 6
};

or:

array<str,3> fields { {
    1, 2, 3, 4, 5, 6
} };

instead.

On the other hand, how you initialize your vector is covered by list initialization. std::vector has a constructor that accepts an std::initializer_list.

The effects of list initialization of an object of type T are:

  • Otherwise, the constructors of T are considered, in two phases:

    • All constructors that take std::initializer_list as the only argument, or as the first argument if the remaining arguments have default values, are examined, and matched by overload resolution against a single argument of type std::initializer_list

Note that you wouldn't be able to initialize your vector ( like this:

vector<str> fields {
    1,2, 3,4, 5,6
};

but:

vector<int> fields {
    1,2, 3,4, 5,6
};

is perfectly fine.



回答2:

It's because array initialization is built a bit different from vector.
To initialize an array you need to use two braces.
Because of a syntax feature you can skip it if you initialize just one object.
So the following is ok:

array{1,2,3} -> array{{1,2,3}}

But in your example you initialize multiple objects so the compiler doesn't add additional braces. Using two braces fixes that.

array<str,3> fields {{
    {1,2}, {3,4}, {5,6}
}};