A std::array
isn’t limited to elements of fundamental types. Rather, the elements of a std::array
can be any object type, including compound types. This means you can create a std::array
of pointers, or a std::array
of structs (or classes)
However, initializing a std::array
of structs or classes tends to trip new programmers up, so we’re going to spend a lesson explicitly covering this topic.
Author’s note
We’ll use structs to illustrate our points in this lesson. The material applies equally well to classes.
Defining and assigning to a std::array
of structs
Let’s start with a simple struct:
Defining a std::array
of House
and assigning elements works just like you’d expect:
The above outputs the following:
House number 13 has 7 rooms. House number 14 has 10 rooms. House number 15 has 8 rooms.
Initializing a std::array
of structs
Initializing an array of structs also works just like you’d expect, so long as you are explicit about the element type:
In the above example, we’re using CTAD to deduce the type of the std::array
as std::array<House, 3>
. We then provide 3 House
objects as initializers, which works just fine.
Initialization without explicitly specifying the element type for each initializer
In the above example, you’ll note that each initializer requires us to list the element type:
But we did not have to do the same in the assignment case:
So you might think to try something like this:
Perhaps surprisingly, this doesn’t work. Let’s explore why.
A std::array
is defined as a struct that contains a single C-style array member (whose name is implementation defined), like this:
Author’s note
We haven’t covered C-style arrays yet, but for the purposes of this lesson, all you need to know is that T implementation_defined_name[N];
is a fixed-size array of N elements of type T (just like std::array<T, N> implementation_defined_name;
).
We cover C-style arrays in upcoming lesson 17.7 -- Introduction to C-style arrays.
So when we try to initialize houses
per the above, the compiler interprets the initialization like this:
The compiler will interpret { 13, 1, 7 }
as the initializer for the first member of houses
, which is the C-style array with the implementation defined name. This will initialize the C-style array element 0 with { 13, 1, 7 }
and the rest of the members will be zero-initialized. Then the compiler will discover we’ve provided two more initialization values ({ 14, 2, 7 }
and { 15, 2, 5 }
) and produce a compilation error telling us that we’ve provided too many initialization values.
The correct way to initialize the above is to add an extra set of braces as follows:
Note the extra set of braces that are required (to begin initialization of the C-style array member inside the std::array
struct). Within those braces, we can then initialize each element individually, each inside its own set of braces.
This is why you’ll see std::array
initializers with an extra set of braces when the element type requires a list of values and we are not explicitly providing the element type as part of the initializer.
Key insight
When initializing a std::array
with a struct, class, or array and not providing the element type with each initializer, you’ll need an extra pair of braces so that the compiler will properly interpret what to initialize.
This is an artifact of aggregate initialization, and other standard library container types (that use list constructors) do not require the double braces in these cases.
Here’s a full example:
Brace elision for aggregates
Given the explanation above, you may be wondering why the above case requires double braces, but all other cases we’ve seen only require single braces:
It turns out that you can supply double braces for such arrays:
However, aggregates in C++ support a concept called brace elision, which lays out some rules for when multiple braces may be omitted. Generally, you can omit braces when initializing a std::array
with scalar (single) values, or when initializing with class types or arrays where the type is explicitly named with each element.
There is no harm in always initializing std::array
with double braces, as it avoids having to think about whether brace-elision is applicable in a specific case or not. Alternatively, you can try to single-brace init, and the compiler will generally complain if it can’t figure it out. In that case, you can quickly add an extra set of braces.
Another example
Here’s one more example where we initialize a std::array
with Student
structs.
This prints:
You found: Joe You found: nobody
Note that because std::array students
is constexpr, our findStudentById()
function must return a const pointer, which means our Student
pointers in main()
must also be const.
Quiz time
Question #1
Define a struct named Item
that contains two members: std::string_view name
and int gold
. Define a std::array
and initialize it with 4 Item objects. Use CTAD to deduce the element type and array size.
The program should print the following:
A sword costs 5 gold. A dagger costs 3 gold. A club costs 2 gold. A spear costs 7 gold.
Question #2
Update your solution to quiz 1 to not explicitly specify the element type for each initializer.