Skip to content
C++
Library
since C++11
Basic

std::array

Fixed-size contiguous array wrapper with value semantics, bounds checking, and full STL compatibility.

std::arraysince C++11

A 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() throws std::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

cpp
#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 access

Examples

Construction and initialization

cpp
#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

cpp
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

cpp
#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);     // false

Generic functions and std::span

cpp
// 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

cpp
// 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:

cpp
auto [width, height] = image.dimensions();  // more readable

Leverage 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

cpp
// 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

cpp
std::array<int, 5> arr = {1, 2, 3};        // OK: remaining elements are 0
std::array<int, 3> arr = {1, 2, 3, 4, 5}; // COMPILER ERROR

Unlike C arrays, std::array enforces exact-size or partial (zero-padded) initialization.

Default-initialization leaves primitives uninitialized

cpp
std::array<int, 10> raw;     // undefined values β€” garbage
std::array<int, 10> clean{}; // all zeros β€” deterministic

Always use {} if determinism matters. Default-construction of primitives is uninitialized.

Move is not O(1) for std::array

cpp
std::array<int, 1000> a = {...};
auto b = std::move(a);  // still O(N) β€” copies all 1000 ints

Unlike 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 a std::array.
  • std::deque β€” Double-ended queue with efficient front insertion/removal.