Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Fixed-Size Arrays with std::array
Use the safer, fixed-size array wrapper from the standard library.
Introduction to std::array
While std::vector excels at runtime-sized collections, its limited constexpr support restricts compile-time optimizations. When you need arrays evaluated at compile time, std::array is the solution.
When fixed-size arrays make sense
Dynamic arrays like std::vector provide flexibility but involve trade-offs:
- Slight performance overhead compared to fixed-size arrays
- Minimal constexpr support (limited to trivial uses)
Modern C++ emphasizes constexpr for safer, more optimizable code. When your array size is known at compile time and you want compile-time evaluation, std::array delivers.
Use `std::array` for constexpr arrays with known sizes. Use `std::vector` for runtime-sized or non-constexpr arrays.
Declaring std::array
std::array resides in the <array> header. Unlike std::vector, it requires the size as a template argument:
#include <array>
#include <vector>
int main()
{
std::array<int, 4> slots{}; // std::array of 4 ints
std::vector<int> items(4); // std::vector of 4 ints (comparison)
return 0;
}
std::array requires two template arguments:
- Element type (
int) - Array length (
4) - a non-type template parameter
The length must be a constant expression
Unlike std::vector which accepts runtime sizes, std::array demands a constant expression for length:
#include <array>
int main()
{
std::array<int, 6> a{}; // Literal constant
constexpr int capacity{ 10 };
std::array<int, capacity> b{}; // Constexpr variable
enum Weapons { sword, bow, staff, max_weapons };
std::array<int, max_weapons> c{}; // Enumerator
return 0;
}
Non-const or runtime values won't compile:
#include <array>
int main()
{
int slots{ 8 };
std::array<int, slots> d{}; // Error: slots is not constexpr
return 0;
}
`std::array` permits zero length, creating an object with no data. Accessing elements of such arrays causes undefined behavior. Use `empty()` to check for this condition.
Aggregate initialization
std::array is an aggregate (it lacks constructors), so it uses aggregate initialization:
#include <array>
int main()
{
std::array<int, 4> levels{ 1, 5, 12, 25 }; // List initialization (preferred)
std::array<int, 3> ranks = { 100, 250, 500 }; // Copy-list initialization
return 0;
}
Elements initialize sequentially from index 0.
Without initializers, elements are default-initialized (often leaving them uninitialized for fundamental types). Always use empty braces for value-initialization:
#include <array>
int main()
{
std::array<int, 4> uninitialized; // Elements may contain garbage
std::array<int, 4> zeroed{}; // All elements are zero (preferred)
return 0;
}
Too many initializers cause compilation errors; too few value-initialize remaining elements:
#include <array>
int main()
{
std::array<int, 2> a{ 10, 20, 30 }; // Error: too many initializers
std::array<int, 5> b{ 10, 20 }; // OK: b[2], b[3], b[4] are zero
return 0;
}
Const and constexpr std::array
std::array can be const:
#include <array>
int main()
{
const std::array<int, 3> thresholds{ 100, 500, 1000 };
// Elements are implicitly const
return 0;
}
More importantly, std::array supports constexpr fully:
#include <array>
int main()
{
constexpr std::array<int, 5> fibonacci{ 1, 1, 2, 3, 5 };
// Evaluated at compile time!
return 0;
}
This constexpr capability is std::array's defining advantage.
Define `std::array` as constexpr whenever possible. If constexpr isn't needed, consider `std::vector` for its flexibility.
Class template argument deduction (C++17)
CTAD allows the compiler to deduce both type and length:
#include <array>
int main()
{
constexpr std::array scores{ 85, 92, 78 }; // Deduces std::array<int, 3>
constexpr std::array weights{ 1.5, 2.3, 0.8 }; // Deduces std::array<double, 3>
return 0;
}
Use CTAD to let the compiler deduce `std::array` type and length from initializers.
CTAD doesn't support partial specification (as of C++23):
#include <array>
int main()
{
std::array<int> a{ 1, 2, 3 }; // Error: length missing
std::array<3> b{ 1, 2, 3 }; // Error: type missing
return 0;
}
Deducing length with std::to_array (C++20)
std::to_array enables length deduction when you need to specify only the type:
#include <array>
int main()
{
constexpr auto arr1{ std::to_array<int, 4>({ 5, 10, 15, 20 }) }; // Both specified
constexpr auto arr2{ std::to_array<int>({ 5, 10, 15, 20 }) }; // Type only
constexpr auto arr3{ std::to_array({ 5, 10, 15, 20 }) }; // Deduce both
return 0;
}
However, std::to_array creates a temporary then copies it, making it more expensive than direct construction. Use it only when CTAD can't determine the type:
#include <array>
int main()
{
// No literal for short, so specify the type explicitly
constexpr auto levels{ std::to_array<short>({ 1, 2, 3, 4 }) };
return 0;
}
Accessing elements
Use operator[] just like std::vector:
#include <array>
#include <iostream>
int main()
{
constexpr std::array<int, 5> ranks{ 10, 25, 50, 100, 200 };
std::cout << ranks[2] << '\n'; // Prints 50
std::cout << ranks[8] << '\n'; // Undefined behavior: out of bounds
return 0;
}
As with other containers, operator[] performs no bounds checking. Invalid indices cause undefined behavior.
Summary
std::array vs std::vector: std::array is a fixed-size array with complete constexpr support, ideal for compile-time arrays. std::vector is dynamic but has minimal constexpr support, making it unsuitable for compile-time operations.
Non-type template parameters: std::array requires two template arguments: element type and array length. The length is a non-type template parameter of type std::size_t that must be a constant expression.
Aggregate initialization: std::array is an aggregate (no constructors), using aggregate initialization. Elements initialize sequentially from index 0. Without initializers, elements are default-initialized; use empty braces for value-initialization.
Constexpr capabilities: std::array has complete constexpr support, enabling compile-time array creation and manipulation. This is its primary advantage over std::vector.
CTAD support (C++17+): Class Template Argument Deduction lets the compiler deduce both element type and array length from initializers, eliminating explicit template arguments.
std::to_array (C++20): Allows length deduction when specifying only the element type. However, it creates and copies a temporary, making it costlier than direct construction. Use only when CTAD can't determine the type.
Zero-length arrays: std::array permits zero length, creating an object with no data. Accessing elements causes undefined behavior. Use empty() to check for this condition.
Choose std::array for constexpr arrays with known, fixed sizes. Otherwise, prefer std::vector for flexibility and move semantics.
Fixed-Size Arrays with std::array - Quiz
Test your understanding of the lesson.
Practice Exercises
Introduction to std::array
Practice using std::array for fixed-size arrays. Learn initialization, element access, and common operations.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!