C++ std::span

Safe and efficient view over contiguous memory sequences

📋 What is std::span?

std::span provides a safe, lightweight view over contiguous sequences like arrays and vectors. It offers bounds checking, size information, and modern C++ interface without owning the data it references.


// std::span example
#include <span>
#include <vector>
#include <iostream>

void print_data(std::span<int> data) {
    std::cout << "Size: " << data.size() << std::endl;
    for (int value : data) {
        std::cout << value << " ";
    }
}

// Works with arrays, vectors, etc.
                                    

Usage:

std::vector<int> vec = {1, 2, 3};

print_data(vec); // Size: 3, Values: 1 2 3

Key std::span Features

ðŸ›Ąïļ

Safe Access

Bounds checking and size awareness

span.at(index);  // Throws on bounds error
span[index];     // Fast access
ðŸŠķ

Lightweight

Just pointer + size, no ownership

// No copying, just references
std::span<int> view = container;
🔄

Universal

Works with arrays, vectors, etc.

void func(std::span<int> data);
// Accepts any contiguous container
✂ïļ

Subviews

Create views of parts of data

auto sub = span.subspan(2, 3);
auto first = span.first(5);

ðŸ”đ Basic std::span Usage

The C++ std::span provides a lightweight, non-owning view over a contiguous sequence of elements, ideal for function parameters. It allows you to process arrays, vectors, or other containers without copying data. For instance, calling a function with a span of 3 elements like {10, 20, 30} or 4 elements like {100, 200, 300, 400} lets the same function handle different sizes safely and efficiently, enhancing code reusability and reducing overhead.

#include <span>
#include <vector>
#include <array>
#include <iostream>

void process_data(std::span<int> data) {
    std::cout << "Processing " << data.size() << " elements: ";
    for (int value : data) {
        std::cout << value << " ";
    }
    std::cout << std::endl;
}

int main() {
    // Works with different container types
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::array<int, 3> arr = {10, 20, 30};
    int c_array[] = {100, 200, 300, 400};
    
    process_data(vec);                    // Vector
    process_data(arr);                    // Array
    process_data(c_array);                // C-style array
    
    return 0;
}

Output:

Processing 5 elements: 1 2 3 4 5

Processing 3 elements: 10 20 30

Processing 4 elements: 100 200 300 400

ðŸ”đ Span Operations

std::span supports various operations to inspect and manipulate the underlying data range. You can check properties like size (e.g., 10 elements) or emptiness (0 for false). It also provides access to specific elements: first() and last() retrieve endpoints, while operator[] accesses any index (like 4 at index 3). Subspans can be created to focus on a middle section, such as elements 3 through 6, enabling flexible, bounds-checked data slicing.

#include <span>
#include <vector>
#include <iostream>

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    std::span<int> span = data;
    
    // Basic properties
    std::cout << "Size: " << span.size() << std::endl;
    std::cout << "Empty: " << span.empty() << std::endl;
    
    // Element access
    std::cout << "First: " << span.front() << std::endl;
    std::cout << "Last: " << span.back() << std::endl;
    std::cout << "At index 3: " << span[3] << std::endl;
    
    // Subviews
    auto first_half = span.first(5);      // First 5 elements
    auto last_half = span.last(5);        // Last 5 elements  
    auto middle = span.subspan(2, 4);     // 4 elements starting at index 2
    
    std::cout << "Middle section: ";
    for (int val : middle) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    
    return 0;
}

Output:

Size: 10

Empty: 0

First: 1, Last: 10, At index 3: 4

Middle section: 3 4 5 6

ðŸ”đ Fixed-Size Spans

Fixed-size spans (std::span<T, N>) enforce compile-time size checking for enhanced safety and optimization. By specifying the exact number of elements (e.g., 3), the compiler can prevent size mismatches and enable better performance. Examples include processing triples like {1, 2, 3}, {4, 5, 6}, or {7, 8, 9}. This is particularly useful in graphics, math libraries, or any domain where data structure size is a critical, known invariant.

#include <span>
#include <array>
#include <iostream>

// Function expecting exactly 3 elements
void process_triple(std::span<int, 3> triple) {
    std::cout << "Processing triple: ";
    for (int val : triple) {
        std::cout << val << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::array<int, 3> arr = {1, 2, 3};
    int c_arr[3] = {4, 5, 6};
    
    process_triple(arr);                  // ✅ Works - exact size
    process_triple(c_arr);                // ✅ Works - exact size
    
    std::vector<int> vec = {7, 8, 9};
    // Convert dynamic span to fixed-size span
    std::span<int> dynamic_span = vec;
    auto fixed_span = dynamic_span.first<3>();
    process_triple(fixed_span);           // ✅ Works
    
    return 0;
}

Output:

Processing triple: 1 2 3

Processing triple: 4 5 6

Processing triple: 7 8 9

🧠 Test Your Knowledge

What does std::span store internally?