Fixed-Size Arrays and C-Style Arrays Recap

This chapter covered the two fixed-size array types in C++: std::array, which is the one to reach for, and the C-style array, which you need to be able to read because it is built into the language and appears throughout older code and C libraries.

A fixed-size array has its length fixed when the object is created and cannot grow or shrink afterwards, which is what separates both of these from std::vector.

Length fixed at Resizable Reach for it when
std::array Compile time No The length is known at compile time and never changes
C-style array Compile time No Interfacing with C code, or reading existing code
std::vector Run time Yes The length is not known until run time, or must change

std::array

std::array is a template struct declared as template <typename T, std::size_t N> struct array;, where N is a non-type template parameter: a template parameter that carries a value rather than a type. Because N is part of the type, the length must be a constant expression, which in practice means a literal, a constexpr variable, or an unscoped enumerator.

It is an aggregate, meaning it has no constructors and is initialized by aggregate initialization with a braced list. Two habits follow from that. Prefer constexpr for the array whenever the contents allow it, and if the array cannot be constexpr, ask whether std::vector is the better fit. Class template argument deduction lets the compiler work out both the element type and the length from the initializers, and pairing it with static_assert on the length gives you a compile-time check that the initializer list has the number of elements you intended.

Length and Indexing

Operation Form Returns Notes
Length arr.size() unsigned size_type Member function
Length std::size(arr) unsigned size_type C++17, calls size() for std::array
Length std::ssize(arr) large signed type C++20, usually std::ptrdiff_t
Index arr[i] element No bounds checking, invalid index is undefined behavior
Index arr.at(i) element Runtime bounds checking
Index std::get<i>(arr) element Index is a template argument, checked at compile time

All three length functions give a constant expression, with one exception: when they are called on a std::array that was passed by reference. That defect was addressed in C++23.

For indexing, prefer std::get when the index is known at compile time, because the check costs nothing at run time. at() is rarely the right answer, since bounds are usually better validated before indexing rather than on every access.

#include <array>
#include <iostream>

int main()
{
    constexpr std::array levels{ 12, 24, 36, 48 };
    static_assert(levels.size() == 4);

    std::cout << "Length: " << std::ssize(levels) << '\n';
    std::cout << "First: " << std::get<0>(levels) << '\n';
    std::cout << "Last: " << levels[levels.size() - 1] << '\n';

    return 0;
}

Output:

Length: 4
First: 12
Last: 48

Passing, Returning, and Brace Elision

Because the length is part of the type, a function that accepts arrays of any length has to be a template, declared template <typename T, std::size_t N>, or in C++20 the shorter template <typename T, auto N>.

Returning a std::array by value copies every element. That is fine for a small array of cheap elements, and an out parameter is worth considering when it is not.

Aggregate initialization needs an extra pair of braces when the element type is itself a struct, class, or array and you do not name the element type with each initializer. This is a quirk of aggregate initialization, and container types that use list constructors do not need it. Brace elision is the set of rules describing when the inner braces may be dropped: broadly, when initializing with plain scalar values, or when the element type is named explicitly for each element.

Arrays of References

An array of references is not allowed, but std::reference_wrapper gives the same effect. Three behaviours are worth remembering: assigning to one reseats it so that it refers to a different object rather than assigning through it, it converts implicitly to T&, and get() returns the underlying T& when you need to modify the referenced object. The helpers std::ref and std::cref construct wrappers without naming the type.

C-Style Arrays

C-style arrays come from C and are part of the core language, which is why they have their own declaration syntax using square brackets. The length inside the brackets is a std::size_t and must be a constant expression. When you supply a full initializer list, omit the length and let the compiler count the elements.

They are aggregates, they can be const or constexpr, and their length is available through std::size in C++17 or std::ssize in C++20. One genuine advantage over the standard library containers: a C-style array can be indexed with a signed integer, an unsigned integer, or an unscoped enumerator, so the sign conversion warnings that come with container indexing do not apply.

Decay and Pointer Arithmetic

In most expressions a C-style array is implicitly converted to a pointer to its first element. This is array decay, and it is why an array loses its length when passed to a function.

Pointer arithmetic applies addition, subtraction, increment, and decrement to a pointer to produce a new address. The step is measured in objects rather than bytes, so for a pointer ptr, the expression ptr + 1 gives the address of the next element of that type. Use subscripting when indexing from the start of the array, so the index and the element number line up, and use pointer arithmetic when positioning relative to some other element.

#include <iostream>

int main()
{
    constexpr int scores[]{ 5, 10, 15, 20 };
    const int* start{ scores };

    std::cout << "Elements: " << std::size(scores) << '\n';
    std::cout << "Third by subscript: " << scores[2] << '\n';
    std::cout << "Third by pointer arithmetic: " << *(start + 2) << '\n';

    return 0;
}

Output:

Elements: 4
Third by subscript: 15
Third by pointer arithmetic: 15

C-Style Strings and Multiple Dimensions

A C-style string is nothing more than a C-style array with element type char or const char, so everything above about decay applies to it as well.

An array's dimension count is how many subscripts it takes to name a single element. One subscript makes it a one-dimensional array, an array of arrays takes two and is a two-dimensional array, and anything past one dimension is a multidimensional array. Flattening collapses those dimensions, usually down to one, by laying every element out in a single contiguous run. C++23 adds std::mdspan, which wraps such a run and lets you subscript it as though it had several dimensions.

Terms Used in This Chapter

  • Fixed-size array (also fixed-length array): length set when the object is created and never changed
  • Dynamic array: length can change while the program runs
  • Constant expression: an expression the compiler can evaluate during compilation
  • Aggregate: a type with no constructors, initialized from a braced list
  • Aggregate initialization: initializing an aggregate member by member from a braced list
  • Class template argument deduction (CTAD): the compiler deducing a class template's arguments from the initializer
  • Non-type template parameter: a template parameter holding a value instead of a type
  • size_type: the unsigned type a container uses for lengths and indices
  • Subscript operator: operator[], which accesses an element without bounds checking
  • Bounds checking: confirming an index is within the valid range before use
  • Brace elision: the rules governing when inner braces may be omitted in aggregate initialization
  • std::reference_wrapper: a copyable, reseatable stand-in for a reference, so references can be stored in containers
  • std::ref and std::cref: helpers that build a std::reference_wrapper or a const one
  • C-style array: the array type built into the core language and inherited from C
  • Array decay: the implicit conversion of an array to a pointer to its first element
  • Pointer arithmetic: arithmetic on a pointer that moves it by whole objects
  • C-style string: a C-style array of char or const char
  • Dimension: how many indices are needed to select an element
  • Flattening: collapsing a multidimensional array into fewer dimensions
  • std::mdspan: the C++23 multidimensional view over a contiguous sequence

Looking Forward

The practical takeaways are to prefer std::array when the length is a compile-time constant, to switch to std::vector the moment it is not, and to recognise decay and pointer arithmetic when you meet them in code that predates the standard containers. Next comes iterators and algorithms, which give you a uniform way to operate on all of these types instead of writing index loops by hand.