In lesson 16.3 --
std::vector and the unsigned length and subscript problem, we discussed the unfortunate decision to make
the standard library container classes use unsigned values for lengths and indices. Because
std::array is a standard library container class, it is subject to the same issues.
In this lesson, we’ll recap ways to index and get the length of a std::array. Because
std::vector and std::array have similar interfaces, this will parallel the what we
covered for std::vector. But since only std::array has full support for constexpr,
we’ll focus a little more on that.
Before proceeding, now would be a good time to refresh your memory on “sign conversions are narrowing conversions, except when constexpr” (see 16.3 -- std::vector and the unsigned length and subscript problem).
The length of a std::array has type
std::size_t
std::array is implemented as a template struct whose declaration looks like this:
As you can see, the non-type template parameter representing the array length (N) has type
std::size_t. And as you’re probably aware by now, std::size_t is a large unsigned
integral type.
Related content
We cover class templates (which includes struct templates) in lesson 13.13 -- Class templates and non-type template parameters in lesson 11.9 -- Non-type template parameters.
Thus, when we define a std::array, the length non-type template argument must either have type
std::size_t, or be convertible to a value of type std::size_t. Because this value must
be constexpr, we don’t run into sign conversion issues when we use a signed integral value, as the compiler will
happily convert a signed integral value to a std::size_t at compile-time without it being
considered a narrowing conversion.
As an aside…
Prior to C++23, C++ didn’t even have a literal suffix for std::size_t, as the implicit
compile-time conversion from int to std::size_t typically suffices for cases where
we need a constexpr std::size_t.
The suffix was added primarily for type deduction purposes, as constexpr auto x { 0 } will give
you an int rather than a std::size_t. In such cases, being able to differentiate
0 (int) from 0UZ (std::size_t) without having to use an
explicit static_cast is useful.
The length and indices of
std::array have type size_type, which is always std::size_t
Just like a std::vector, std::array defines a nested typedef member named
size_type, which is an alias for the type used for the length (and indices, if supported) of the
container. In the case of std::array, size_type is always an alias for
std::size_t.
Note that the non-type template parameter defining the length of the std::array is explicitly
defined as std::size_t rather than size_type. This is because size_type
is a member of std::array, and isn’t defined at that point. This is the only place that uses
std::size_t explicitly -- everywhere else uses size_type.
Getting the length of a std::array
There are three common ways to get the length of a std::array object.
First, we can ask a std::array object for its length using the size() member function
(which returns the length as unsigned size_type):
This prints:
length: 5
Unlike std::string and std::string_view, which have both a length() and
a size() member function (that do the same thing), std::array (and most other
container types in C++) only have size().
Second, in C++17, we can use the std::size() non-member function (which for
std::array just calls the size() member function, thus returning the length as
unsigned size_type).
Finally, in C++20, we can use the std::ssize() non-member function, which returns the length as a
large signed integral type (usuallystd::ptrdiff_t):
This is the only function of the three which returns the length as a signed type.
Getting the length of a std::array as a
constexpr value
Because the length of a std::array is constexpr, each of the above functions will return the
length of a std::array as a constexpr value (even when called on a non-constexpr
std::array object)! This means we can use any of these functions in constant expressions, and the
length returned can be implicitly converted to an int without it being a narrowing conversion:
For Visual Studio users
Visual Studio incorrectly triggers warning C4365 for the above example. The issue has been reported to Microsoft.
Warning
Due to a language defect, the above functions will return a non-constexpr value when called on a
std::array function parameter passed by (const) reference:
This defect has been addressed in C++23 by P2280. At the time of writing, few compilers currently support this feature.
A workaround is to make foo() a function template where the array length is a non-type template
parameter. This non-type template parameter can then be used inside the function. We discuss this further in
lesson 17.3 -- Passing and
returning std::array.
Subscripting std::array using
operator[] or the at() member function
In the prior lesson 17.1 --
Introduction to std::array, we covered that the most common way to index a std::array is to
use the subscript operator (operator[]). No bounds checking is done in this case, and passing in an
invalid index will result in undefined behavior.
Just like std::vector, std::array also has an at() member function that
does subscripting with runtime bounds checking. We recommend avoiding this function since we typically want to
do bounds checking before indexing, or we want compile-time bounds checking.
Both of these functions expect the index to be of type size_type (std::size_t).
If either of these functions are called with a constexpr value, the compiler will do a constexpr conversion to
std::size_t. This isn’t considered to be a narrowing conversion, so you won’t run into sign
problems here.
However, if either of these functions are called with a non-constexpr signed integral value, the conversion to
std::size_t is considered narrowing and your compiler may emit a warning. We discuss this case
further (using std::vector) in lesson 16.3 --
std::vector and the unsigned length and subscript problem.
std::get() does compile-time bounds checking
for constexpr indices
Since the length of a std::array is constexpr, if our index is also a constexpr value, then the
compiler should be able to validate at compile-time that our constexpr index is within the bounds of the array
(and stop compilation if the constexpr index is out of bounds).
However, operator[] does no bounds checking by definition, and the at() member
function only does runtime bounds checking. And function parameters can’t be constexpr (even for constexpr or
consteval functions), so how do we even pass a constexpr index?
To get compile-time bounds checking when we have a constexpr index, we can use the std::get()
function template, which takes the index as a non-type template argument:
Inside the implementation of std::get(), there is a static_assert that checks to ensure that the
non-type template argument is smaller than the array length. If it isn’t, then the static_assert will halt the
compilation process with compilation error.
Since template arguments must be constexpr, std::get() can only be called with constexpr indices.