Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Fixed-Size Arrays and C-Style Arrays Summary
Review and test your understanding of all fixed-size array and C-style array concepts covered in this chapter.
Fixed-size Arrays and C-style Arrays recap
Excellent work! You've learned about arrays and strings in C++, including both modern (std::array) and legacy (C-style arrays) approaches. Let's review the key concepts from this section.
Fixed-Size vs Dynamic Arrays
Fixed-size arrays (or fixed-length arrays) require that the length of the array be known at the point of instantiation, and that length cannot be changed afterward. C-style arrays and std::array are both fixed-size arrays. Dynamic arrays can be resized at runtime. std::vector is a dynamic array.
std::array Basics
The length of a std::array must be a constant expression. Most often, the value provided for the length will be an integer literal, constexpr variable, or an unscoped enumerator.
std::array is an aggregate. This means it has no constructors, and instead is initialized using aggregate initialization.
Define your std::array as constexpr whenever possible. If your std::array is not constexpr, consider using a std::vector instead.
Use class template argument deduction (CTAD) to have the compiler deduce the type and length of a std::array from its initializers.
std::array Implementation
std::array is implemented as a template struct whose declaration looks like this:
template<typename T, std::size_t N> // N is a non-type template parameter
struct array;
The non-type template parameter representing the array length (N) has type std::size_t.
Getting Array Length
To get the length of a std::array:
- We can ask a
std::arrayobject for its length using thesize()member function (which returns the length as unsignedsize_type). - In C++17, we can use the
std::size()non-member function (which forstd::arrayjust calls thesize()member function, thus returning the length as unsignedsize_type). - 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).
All three of these functions will return the length as a constexpr value, except when called on a std::array passed by reference (this defect has been addressed in C++23).
Indexing std::array
To index a std::array:
- Use the subscript operator (
operator[]). No bounds checking is done in this case, and passing in an invalid index will result in undefined behavior. - Use the
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. - Use the
std::get()function template, which takes the index as a non-type template argument, and does compile-time bounds checking.
Function Templates with std::array
You can pass std::array with different element types and lengths to a function using a function template with the template parameter declaration template <typename T, std::size_t N>. Or in C++20, use template <typename T, auto N>.
Returning a std::array by value will make a copy of the array and all elements, but this may be okay if the array is small and the elements aren't expensive to copy. Using an out parameter instead may be a better choice in some contexts.
Aggregate Initialization and Brace Elision
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.
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.
std::reference_wrapper
You cannot have an array of references, but you can have an array of std::reference_wrapper, which behaves like a modifiable lvalue reference.
There are a few things worth noting about std::reference_wrapper:
Operator=will reseat astd::reference_wrapper(change which object is being referenced).std::reference_wrapper<T>will implicitly convert toT&.- The
get()member function can be used to get aT&. This is useful when we want to update the value of the object being referenced.
The std::ref() and std::cref() functions were provided as shortcuts to create std::reference_wrapper and const std::reference_wrapper wrapped objects.
Compile-Time Validation
Use static_assert whenever possible to ensure a constexpr std::array using CTAD has the correct number of initializers.
C-Style Arrays
C-style arrays were inherited from the C language, and are built into the core language of C++. Because they are part of the core language, C-style arrays have their own special declaration syntax. In a C-style array declaration, we use square brackets ([]) to tell the compiler that a declared object is a C-style array. Inside the square brackets, we can optionally provide the length of the array, which is an integral value of type std::size_t that tells the compiler how many elements are in the array. The length of a C-style array must be a constant expression.
C-style arrays are aggregates, which means they can be initialized using aggregate initialization. When using an initializer list to initialize all elements of a C-style array, it's preferable to omit the length and let the compiler calculate the length of the array.
C-style arrays can be indexed via operator[]. The index of a C-style array can be either a signed or an unsigned integer, or an unscoped enumeration. This means that C-style arrays are not subject to all of the sign conversion indexing issues that the standard library container classes have!
C-style arrays can be const or constexpr.
C-Style Array Length
To get the length of a C-style array:
- In C++17, we can use the
std::size()non-member function, which returns the length as unsignedstd::size_t. - 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).
Array Decay
In most cases, when a C-style array is used in an expression, the array will be implicitly converted into a pointer to the element type, initialized with the address of the first element (with index 0). This is called array decay (or just decay).
Pointer Arithmetic
Pointer arithmetic is a feature that allows us to apply certain integer arithmetic operators (addition, subtraction, increment, or decrement) to a pointer to produce a new memory address. Given some pointer ptr, ptr + 1 returns the address of the next object in memory (based on the type being pointed to).
Use subscripting when indexing from the start of the array (element 0), so the array indices line up with the element. Use pointer arithmetic when doing relative positioning from a given element.
C-Style Strings
C-style strings are just C-style arrays whose element type is char or const char. As such, C-style strings will decay.
Multidimensional Arrays
The dimension of an array is the number of indices needed to select an element.
An array containing only a single dimension is called a single-dimensional array or a one-dimensional array (sometimes abbreviated as a 1d array). An array of arrays is called a two-dimensional array (sometimes abbreviated as a 2d array) because it has two subscripts. Arrays with more than one dimension are called multidimensional arrays. Flattening an array is a process of reducing the dimensionality of an array (often down to a single dimension).
In C++23, std::mdspan is a view that provides a multidimensional array interface for a contiguous sequence of elements.
Key Terminology
- Fixed-size array: Array whose length is known at instantiation and cannot change
- Fixed-length array: Another name for fixed-size array
- Dynamic array: Array that can be resized at runtime
- Constant expression: Expression that can be evaluated at compile-time
- Aggregate: Type with no constructors that uses aggregate initialization
- Aggregate initialization: Initialization using braced initializer list
- Class template argument deduction (CTAD): Compiler deduction of template arguments
- Non-type template parameter: Template parameter that is a value rather than a type
- size_type: Unsigned type used for container lengths and indices
- Subscript operator: operator[] for accessing array elements
- Bounds checking: Runtime verification that an index is valid
- Brace elision: Rules for omitting braces in aggregate initialization
- std::reference_wrapper: Wrapper allowing reference-like behavior in containers
- std::ref(): Function creating std::reference_wrapper
- std::cref(): Function creating const std::reference_wrapper
- C-style array: Array syntax inherited from C language
- Array decay: Implicit conversion of array to pointer to first element
- Pointer arithmetic: Arithmetic operations on pointers to navigate memory
- C-style string: C-style array of char or const char
- Dimension: Number of indices needed to select an array element
- Single-dimensional array: Array with one dimension
- Two-dimensional array: Array of arrays (2d array)
- Multidimensional array: Array with more than one dimension
- Flattening: Reducing array dimensionality
- std::mdspan: C++23 view providing multidimensional array interface
Looking Forward
You've built a strong foundation in arrays and strings in C++. Understanding both modern std::array and legacy C-style arrays is important, as you'll encounter both in real-world code. The principles you've learned—preferring std::array over C-style arrays, understanding array decay, and using proper indexing techniques—will serve you throughout your C++ journey. Next, we'll explore iterators and algorithms, which provide powerful ways to work with arrays and other container types.
Fixed-Size Arrays and C-Style Arrays Summary - Quiz
Test your understanding of the lesson.
Practice Exercises
Matrix Operations
Implement matrix operations using std::array and multidimensional arrays. This capstone exercise covers fixed-size arrays, array passing, pointer arithmetic, and C-style array concepts.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!