std::array无initializer_list构造函数时的初始化机制及类成员疑问
Great questions—let's unpack each one clearly:
1. How does std::array initialization work?
First things first: std::array is an aggregate type in C++. That's the core reason its initialization works differently from containers like std::vector, which have explicit constructors for std::initializer_list.
When you write std::array<int, 4> a = {1,2,3,4};, this isn't calling a constructor that takes an initializer list—because std::array doesn't have one. Instead, this is aggregate initialization: the braced list {1,2,3,4} directly initializes the underlying raw C-style array that std::array wraps. The standard requires std::array to be laid out in memory exactly like a raw array, so this initialization behaves just like int a[4] = {1,2,3,4};.
The braced list here isn't treated as a std::initializer_list<int>; it's a list of values to assign to each element of the aggregate (the internal array) in order. If you provide fewer values than the array size, the remaining elements get value-initialized (for int, that means set to 0). And since C++11, you can even drop the = and write std::array<int,4> a{1,2,3,4};—same behavior.
2. Where is the actual data stored in libstdc++'s std::array?
Libstdc++ uses a helper template __array_traits to handle std::array implementation, and it has two key specializations:
- For non-zero size arrays (
N > 0):__array_traits<T, N>::_Tpis defined asT[N]—a raw C-style array. So when you see_M_elemsdeclared as__array_traits::_Tp _M_elems;, that's equivalent toT _M_elems[N];—this is the actual storage for your elements, embedded directly in thestd::arrayobject (no dynamic allocation here). - For zero-size arrays (
N = 0): Since raw arrays of size 0 aren't allowed in C++, libstdc++ uses a dummy type, and the_S_reffunction returns a null pointer as a placeholder. But this is an edge case you'll rarely encounter.
If you search for the __array_traits specialization for N != 0, you'll see it explicitly defines _Tp as the raw array type. That's where your data lives—right as a member of the std::array struct.
3. Why braces instead of parentheses for initializing the std::array member?
The short answer: std::array has no user-defined constructors that accept individual arguments, so parentheses won't work—but aggregate initialization with braces will.
When you write values_{std::forward<Args>(args)...}, you're using aggregate initialization again to directly populate the underlying raw array inside values_. Aggregate initialization accepts a braced list of elements (even a single element for Point<1>), so this works seamlessly.
On the flip side, values_(std::forward<Args>(args)...) tries to call a constructor of std::array that matches the forwarded arguments. But std::array only has the default constructor, copy constructor, and move constructor—no constructor that takes a single double (for Point<1>) or three doubles (for Point<3>). Hence the compiler error about no matching function.
As for std::forward: it preserves the value category of the arguments. For primitive types like double, this doesn't make a practical difference, but for non-primitive types, it ensures that rvalues (like temporaries) are moved instead of copied—keeping your generic code efficient and correct.
内容的提问来源于stack exchange,提问作者tmaric

