A Safer, Fixed Array with std::array
C-style arrays come with some limitations. You can’t copy one into another with the = operator, or compare them with ==. When an array decays after being passed to a function, you have to pass the size along separately and keep the two in sync yourself.
std::array is a safer, more convenient alternative to the C-style array that fills these gaps and supports other features.
Declaring and initializing an std::array
std::array is defined in the <array> header. It is declared by specifying two things inside angle brackets: the type of the elements, and the array’s size:
#include <array>std::array<double, 4> amounts = {120.5, 98.0, 150.25, 175.0};Here, amounts has the type std::array<double, 4>. It means “a fixed array of 4 doubles.” The type is fixed at compile time for the duration of the program’s life.
Safe indexing with .at()
Underneath, std::array is really just a C-style array, so the memory layout is identical. The [] operator is used to index an individual element, as before, and there is no out of bounds checking:
std::array<double, 4> amounts = {120.5, 98.0, 150.25, 175.0};std::cout << amounts[10]; // Undefined behavior, no error.at() is the safer alternative, as it checks the index every time and throws an exception if you go out of bounds. So, the following stops the program with an error:
Exceptions like these can be handled gracefully later.
Copying and comparing arrays directly
With std::array, you can use the assignment operator or a comparison operator as shown:
You can also apply other comparison operators like <, <=, >, >=, and != as long as the operator is supported on the type of elements stored in the array. The comparison works lexicographically, meaning it compares element by element from the start. The first pair of elements that differ decides the result.
Size stored with the array
Every std::array keeps track of its own size with size(). So, a function receiving an array can access its size, without needing it passed in explicitly:
Passing std::array to functions
The previous example passed the array by value, which is fine for small arrays. For larger ones, passing by const reference avoids copying the whole thing:
void print_all(const std::array<double, 4>& amounts) { for (std::size_t i = 0; i < amounts.size(); i++) { std::cout << amounts[i] << '\n'; }}i is declared as std::size_t here since that’s what size() returns (same as with size() defined for other container types).
Since std::array<double, 4> and std::array<double, 8> are two different types, a function written to take one won’t accept the other.
Iterating
Range-based for works exactly as you’d expect, same as with a C-style array:
std::array<double, 4> amounts = {120.5, 98.0, 150.25, 175.0};for (double a : amounts) { std::cout << a << '\n';}Filling and swapping
std::array gives you some conveniences for common operations, like:
fill()sets every element to the same value in one call, which is useful for resetting.swap()exchanges the contents of two arrays of the same type and size:
When you’d still opt for a C-style array
There’s seldom a good reason to pick a C-style array over std::array. The main exception is interfacing with older code that expects a traditional array. Even then, std::array has a .data() method that hands over direct access to the underlying storage, so you can bridge the gap without giving up the safety everywhere else in your code:
std::array<double, 4> amounts = {120.5, 98.0, 150.25, 175.0};legacy_c_function(amounts.data(), amounts.size());Checkpoint
What does this print?
std::array<int, 3> a1 = {1, 5, 9};
std::array<int, 3> a2 = {1, 6, 2};
std::cout << (a1 < a2);You have two arrays declared as std::array<int, 3> a1 = {1, 2, 3}; and std::array<int, 3> a2;. What happens after running a2 = a1;?
Given this code, what happens?
std::array<int, 3> a1 = {1, 2, 3};
std::array<int, 5> a2;
a2 = a1;What does calling .fill(7) on a std::array<int, 4> do?