Skip to content

Latest commit

 

History

History
249 lines (188 loc) · 12.5 KB

stdarray.md

File metadata and controls

249 lines (188 loc) · 12.5 KB

std::array


Executive Abstract

std::array<T, N> is a fixed-size sequence container template that encapsulates a statically allocated raw C-style array of $N$ elements of type $T$. Introduced in C++11 and enhanced with Class Template Argument Deduction (C++17), 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.


Motivation & Historical Context

Pre-C++11 Deficiencies: Raw C-Style Array Vulnerabilities

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!
}

Deficiencies of Pre-C++11 C-Style Arrays:

  1. 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.
  2. Lack of First-Class Object Semantics: Raw arrays cannot be assigned (a2 = a1 is illegal), copied by value, or returned by value from functions.
  3. No Bounds Checking or Container Interface: Lacked member typedefs (value_type, size_type), bounds checking (.at()), iterator functions (.begin(), .end()), or .size() queries.
  4. std::vector Overkill: Using std::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.


Technical Mechanics & Memory Representation

1. Aggregate Class Structure

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};).


2. Zero-Allocation Stack Memory Layout

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::array on the stack places all $N$ elements on the stack frame. Allocating std::array as a class data member embeds the array contiguous memory directly into the parent class layout.

3. Tuple Interface Protocol

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$.

Evolution Across Standards (C++11 to C++26/C++29)

┌──────────────────────────────────────────────────────────────────┐
│ 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                         │
└──────────────────────────────────────────────────────────────────┘

Comprehensive Code Examples

Example 1: First-Class Value Semantics, Bounds Checking, and Value Returns

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;
}

Example 2: Modern C++17 CTAD & Structured Bindings

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;
}

Example 3: C++20 std::to_array and C++23 Range Formatting

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;
}

Engineering Best Practices & Common Pitfalls

  1. Replace Raw C-Style Arrays Completely:

    • Prefer std::array<T, N> over T arr[N] in all modern C++ codebases. It provides identical runtime assembly performance with complete type safety.
  2. 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, use std::vector<T>.
  3. Interoperability with C APIs:

    • To pass std::array into legacy C functions expecting raw pointers (T*), use .data():
      std::array<char, 256> buffer;
      c_api_read_bytes(buffer.data(), buffer.size());

Relationships

  • Related to:
    • std::vector — Dynamic heap-allocated sequence container.
    • std::span (C++20) — Non-owning contiguous view over std::array or raw buffers.
    • std::to_array (C++20) — Factory function constructing std::array from raw arrays.
  • Superseded by: None.
  • Supersedes: Raw C-style arrays (T[N]).

Back to C++11 section.