std::array
Fixed-size contiguous array wrapper with value semantics, bounds checking, and full STL compatibility.
std::arraysince C++11A statically-sized contiguous array container with stack storage, value semantics, optional bounds checking via at(), and full STL algorithm integration.
Overview
std::array<T, N> wraps a C-style array T[N] with type safety and composability while maintaining zero-overhead abstraction. It allocates N * sizeof(T) bytes on the stack, knows its own size, doesn't decay to a pointer, and integrates seamlessly with STL algorithms and structured bindings.
Key characteristics:
- Storage: Stack-allocated; size is a compile-time constant
- Value semantics: Full copy and move support (unlike raw arrays which decay to pointers)
- Size: Immutable and
constexpr; known at compile time - Bounds checking:
operator[]is unchecked;at()throwsstd::out_of_range - No overhead: Memory layout identical to
T[N], zero runtime cost versus raw arrays - STL integration: Works with all range algorithms and iterators
Syntax
#include <array>
// Declaration and initialization
std::array<T, N> arr; // uninitialized (for primitives)
std::array<T, N> arr{}; // zero-initialized
std::array<T, N> arr = {v0, v1, v2}; // aggregate initialization
// C++20: deduction guide
std::array arr = {1, 2, 3}; // deduces std::array<int, 3>
// Key member functions (all C++11+)
arr.size(); // constexpr std::size_t
arr.max_size(); // same as size()
arr.empty(); // constexpr; false if N > 0
arr.data(); // T* to underlying buffer
arr.fill(value); // assigns to all elements
arr.begin(), arr.end(); // iterators (const variants available)
arr.at(i); // bounds-checked access
arr[i]; // unchecked accessExamples
Construction and initialization
#include <array>
// Aggregate initialization (C++11)
std::array<int, 3> a = {1, 2, 3};
std::array<int, 3> b{4, 5, 6};
// Default and zero-initialization
std::array<int, 5> raw; // uninitialized β garbage
std::array<int, 5> clean{}; // zero-initialized β all 0
// Fill and copy/move (C++11)
std::array<double, 4> filled;
filled.fill(3.14);
auto copy = a; // copy constructor
auto moved = std::move(a); // move constructor (still O(N) for primitives)Access and bounds checking
std::array<int, 5> arr = {10, 20, 30, 40, 50};
// Unchecked access β no bounds checking
int x = arr[2]; // 30
// Bounds-checked access β throws std::out_of_range
try {
int y = arr.at(10);
} catch (const std::out_of_range& e) {
std::cerr << "Index out of range\n";
}
// Direct pointer to underlying data
int* ptr = arr.data();
// Tuple-like access by compile-time index (C++11)
int first = std::get<0>(arr); // alternative to arr[0]
// Structured bindings (C++17)
auto [x, y, z] = std::array{1, 2, 3};Iteration and algorithms
#include <array>
#include <algorithm>
std::array<int, 5> arr = {5, 3, 1, 4, 2};
// Range-based for (C++11)
for (int n : arr) {
std::cout << n << ' ';
}
// STL algorithms β legacy style (C++11)
std::sort(arr.begin(), arr.end());
std::find(arr.begin(), arr.end(), 3);
// Range algorithms (C++20)
std::ranges::sort(arr);
std::ranges::reverse(arr);
auto it = std::ranges::find(arr, 3);
// Element-wise and lexicographic comparisons (C++11)
std::array<int, 3> a1 = {1, 2, 3};
std::array<int, 3> a2 = {1, 2, 3};
bool equal = (a1 == a2); // true
bool less = (a1 < a2); // falseGeneric functions and std::span
// Accept std::array of any size (C++11)
template<size_t N>
void process(const std::array<int, N>& arr) {
// N is known at compile time
}
// Accept any contiguous range β more flexible (C++20)
void process(std::span<const int> data) {
// works with std::array, std::vector, C arrays, pointers
for (const auto& elem : data) {
// process elem
}
}
// Size-agnostic iteration
template<typename T, size_t N>
void print(const std::array<T, N>& arr) {
for (const auto& elem : arr) {
std::cout << elem << ' ';
}
}Multi-dimensional arrays
// 4 rows Γ 3 columns
std::array<std::array<int, 3>, 4> grid{};
grid[1][2] = 42;
// Nested iteration
for (const auto& row : grid) {
for (int val : row) {
std::cout << val << ' ';
}
std::cout << '\n';
}Best Practices
Use at() for untrusted indices. operator[] offers no bounds checking; prefer at() when the index comes from user input or external data.
Reach for std::span in generic code. If your function doesn't require the exact compile-time size, accept std::span<T> (C++20) instead of std::array<T, N> to work with vectors, C arrays, and other contiguous sequences.
Stack-allocate small, fixed-size data. std::array is ideal for compile-time-sized buffers: parsing stacks, local work buffers, cache-friendly small tables. For dynamic sizes, use std::vector.
Use structured bindings to unpack results. When decomposing small fixed-size tuples or arrays, structured bindings (C++17) are clearer than explicit indexing:
auto [width, height] = image.dimensions(); // more readableLeverage constexpr computations. Sizes, comparisons, and data() are all constexpr-eligible; use this for compile-time metaprogramming and constant expressions.
Common Pitfalls
Stack overflow from over-sized arrays
// WRONG: allocates 4 MB on the stack
std::array<int, 1'000'000> huge;
// CORRECT: heap allocation
auto huge = std::make_unique<std::array<int, 1'000'000>>();
std::vector<int> huge(1'000'000);Typical stack size is 1β8 MB; large fixed arrays easily cause stack overflow.
Aggregate initialization truncation
std::array<int, 5> arr = {1, 2, 3}; // OK: remaining elements are 0
std::array<int, 3> arr = {1, 2, 3, 4, 5}; // COMPILER ERRORUnlike C arrays, std::array enforces exact-size or partial (zero-padded) initialization.
Default-initialization leaves primitives uninitialized
std::array<int, 10> raw; // undefined values β garbage
std::array<int, 10> clean{}; // all zeros β deterministicAlways use {} if determinism matters. Default-construction of primitives is uninitialized.
Move is not O(1) for std::array
std::array<int, 1000> a = {...};
auto b = std::move(a); // still O(N) β copies all 1000 intsUnlike std::vector, moving a std::array is a full element-wise copy because the size is part of the type. For true zero-cost moves with dynamic-sized containers, use std::vector or move pointers/references instead.
See Also
std::vectorβ Dynamic-sized contiguous container; use when size is not known at compile time.std::span(C++20) β Non-owning view of contiguous sequences; prefer for generic functions.std::get<I>()β Tuple-like access by compile-time index.std::tuple_sizeβ Type trait for obtaining the size of astd::array.std::dequeβ Double-ended queue with efficient front insertion/removal.