std::array<T, N> is a fixed-size sequence container template that encapsulates a statically allocated raw C-style array of std::to_array (C++20), and range formatting (C++23), std::array bridges raw pointer performance with C++ Standard Template Library (STL) safety. It maintains zero dynamic allocation overhead, fixed stack placement, and cache-line contiguous storage while providing value copy/assignment semantics, bounds checking (.at()), iterator interface traversal, and tuple-like decomposition.
In C++98/C++03, fixed-size contiguous memory blocks were expressed using raw C-style arrays (T arr[N]):
// C++98 Raw C-Style Array Vulnerabilities
void process_buffer(int arr[10]) { // Decay Bug: 'arr' silently decays to 'int*' pointer!
// sizeof(arr) returns 8 (pointer size) instead of 40 (array byte size)!
}
int main() {
int a1[3] = {1, 2, 3};
int a2[3];
// a2 = a1; // COMPILE ERROR! Raw arrays cannot be assigned!
// int[3] get_data(); // COMPILE ERROR! Raw arrays cannot be returned by value!
}-
Implicit Pointer Decay: Raw arrays decay implicitly to raw memory pointers (
T*) when passed across function call boundaries, losing length metadata ($N$ ) and type safety. -
Lack of First-Class Object Semantics: Raw arrays cannot be assigned (
a2 = a1is illegal), copied by value, or returned by value from functions. -
No Bounds Checking or Container Interface: Lacked member typedefs (
value_type,size_type), bounds checking (.at()), iterator functions (.begin(),.end()), or.size()queries. -
std::vectorOverkill: Usingstd::vector<T>for small, fixed-size stack arrays incurred unnecessary dynamic heap allocation (malloc/free), pointer dereferences, and allocator fragmentation.
Solution: C++11 introduced std::array<T, N>, an aggregate wrapper around raw arrays providing full STL container semantics with zero runtime performance cost.
std::array is specified as an Aggregate Class containing a direct, un-encapsulated raw array field:
template <typename T, std::size_t N>
struct array {
T _M_elems[N]; // Direct contiguous raw array member
// STL Iterator & Element Access API...
};Because std::array is an aggregate (no user-declared constructors), it supports Brace-Initialization (std::array<int, 3> a{1, 2, 3};).
Elements are stored sequentially in contiguous memory directly within the memory footprint of the std::array object:
std::array<int, 4> Memory Layout
┌───────────────────┬───────────────────┬───────────────────┬───────────────────┐
│ Element [0] │ Element [1] │ Element [2] │ Element [3] │
├───────────────────┼───────────────────┼───────────────────┼───────────────────┤
│ 4 Bytes │ 4 Bytes │ 4 Bytes │ 4 Bytes │
└───────────────────┴───────────────────┴───────────────────┴───────────────────┘
▲ ▲
└──────────────────────────── Total Size: 16 Bytes ─────────────────────────────┘
-
Memory Overhead: Zero overhead.
sizeof(std::array<T, N>) == sizeof(T) * N. -
Placement: Allocating
std::arrayon the stack places all$N$ elements on the stack frame. Allocatingstd::arrayas a class data member embeds the array contiguous memory directly into the parent class layout.
std::array integrates with the C++ tuple interface, enabling compile-time indexing and structured bindings:
-
std::get<I>(arr): Accesses element at index$I$ at compile time. -
std::tuple_size<std::array<T, N>>::value: Returns size$N$ . -
std::tuple_element<I, std::array<T, N>>::type: Returns type$T$ .
┌──────────────────────────────────────────────────────────────────┐
│ C++11 │
├──────────────────────────────────────────────────────────────────┤
│ • Introduced std::array<T, N> template container │
│ • Full STL container interface (begin, end, at, data, size) │
│ • Tuple API integration (std::get, std::tuple_size) │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ C++17 │
├──────────────────────────────────────────────────────────────────┤
│ • Class Template Argument Deduction (CTAD): std::array arr{1,2,3}│
│ • Structured Bindings support (auto [x, y, z] = arr;) │
│ • constexpr member functions expanded │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ C++20 │
├──────────────────────────────────────────────────────────────────┤
│ • std::to_array utility (Constructs std::array from raw arrays) │
│ • All member functions become fully constexpr-capable │
└──────────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────┐
│ C++23 / C++26 │
├──────────────────────────────────────────────────────────────────┤
│ • Native Range Formatting support: std::print("{}", my_array) │
│ • Extended constexpr evaluation coverage │
└──────────────────────────────────────────────────────────────────┘
Demonstrates passing, returning, and assigning std::array by value without pointer decay.
#include <iostream>
#include <array>
#include <stdexcept>
// Fixed array returned BY VALUE from function cleanly!
constexpr std::array<int, 3> create_identity_row() {
return std::array<int, 3>{1, 0, 0};
}
void process_array(std::array<int, 3> arr) { // Passed by value (Value copy!)
std::cout << "Element [0]: " << arr[0] << "\n";
}
int main() {
// 1. Return by value from function
std::array<int, 3> row = create_identity_row();
// 2. Direct Value Assignment (Impossible with raw arrays!)
std::array<int, 3> row_copy;
row_copy = row;
// 3. Bounds-Checked Element Access (.at() throws std::out_of_range)
try {
std::cout << "Index 1: " << row.at(1) << "\n";
int out_of_bounds = row.at(10); // Throws exception!
} catch (const std::out_of_range& e) {
std::cerr << "Caught Expected Bounds Exception: " << e.what() << "\n";
}
return 0;
}Demonstrates Class Template Argument Deduction (CTAD) and decomposing arrays using structured bindings.
#include <iostream>
#include <array>
int main() {
// C++17 CTAD: Type std::array<double, 3> deduced automatically!
std::array coords{1.5, 2.5, 3.5};
// C++17 Structured Bindings via std::array tuple interface
auto [x, y, z] = coords;
std::cout << "Decomposed Coords: X=" << x << ", Y=" << y << ", Z=" << z << "\n";
// Modifying elements via reference structured bindings
auto& [ref_x, ref_y, ref_z] = coords;
ref_x = 10.0;
std::cout << "Modified coords[0] via binding: " << coords[0] << " (Expected: 10.0)\n";
return 0;
}Demonstrates std::to_array constructing std::array from string literals and formatting output directly.
#include <iostream>
#include <array>
#include <utility>
int main() {
// 1. C++20 std::to_array converting C-style array to std::array with deduced type and length
int raw_buffer[] = {10, 20, 30, 40, 50};
auto arr = std::to_array(raw_buffer); // Deduced as std::array<int, 5>
static_assert(std::is_same_v<decltype(arr), std::array<int, 5>>);
// 2. Converting string literals cleanly
auto char_arr = std::to_array("C++20"); // Deduced as std::array<char, 6>
std::cout << "Array Size: " << arr.size() << "\n";
std::cout << "Char Array Last Char: " << static_cast<int>(char_arr[5]) << " (Null Terminator)\n";
return 0;
}-
Replace Raw C-Style Arrays Completely:
- Prefer
std::array<T, N>overT arr[N]in all modern C++ codebases. It provides identical runtime assembly performance with complete type safety.
- Prefer
-
Stack Overflow Risk with Large Arrays:
- Allocating a massive array on the stack (e.g.,
std::array<double, 1'000'000> local_buffer;) will cause an immediate stack overflow runtime crash. For large, variable, or dynamically sized data, usestd::vector<T>.
- Allocating a massive array on the stack (e.g.,
-
Interoperability with C APIs:
- To pass
std::arrayinto legacy C functions expecting raw pointers (T*), use.data():std::array<char, 256> buffer; c_api_read_bytes(buffer.data(), buffer.size());
- To pass
- Related to:
std::vector— Dynamic heap-allocated sequence container.std::span(C++20) — Non-owning contiguous view overstd::arrayor raw buffers.std::to_array(C++20) — Factory function constructingstd::arrayfrom raw arrays.
- Superseded by: None.
- Supersedes: Raw C-style arrays (
T[N]).
Back to C++11 section.