Exploring C++ std::span – Part 1: An Introduction To the Non-Owning View

C++ std::span

What is std::span?

std::span is a feature introduced in C++20, designed to provide a safe and efficient way to create a “view” over contiguous sequences of data, such as arrays, std::vectors, or std::arrays. Unlike traditional containers, std::span is non-owning; it doesn’t manage the memory of the data it views. Instead, std::span provides a lightweight interface for accessing existing data without copying it.

This feature is particularly useful when you want to pass parts of an array or a vector to functions without incurring the cost of copying data. Here’s an overview of how std::span fits into modern C++ programming.

Key Benefits of std::span

  • Non-owning: std::span does not copy or take ownership of the underlying data, making it ideal for function parameters or temporary views.
  • Lightweight: A std::span instance is small and efficient to copy by value, typically as small as a pointer and a size.
  • Type-Safe: std::span provides bounds-safe access to contiguous data, which can reduce common memory access errors.

When to Use std::span

std::span is ideal for situations where:

  • You need a non-owning view of an existing array, vector, or other contiguous memory structure.
  • A function should accept data from multiple types of containers (e.g., std::array, raw arrays, or std::vector).
  • You want a safer alternative to raw pointers for accessing array data.

Basic Syntax and Usage of std::span

Let’s look at some examples of how to create and use std::span in C++.

1. Constructing a std::span from a C-style Array

#include <span>
#include <iostream>

int main() {
    int data[] = {1, 2, 3, 4, 5};
    
    // Create a span over the entire array
    std::span<int> mySpan(data);

    // Access elements
    for (int element : mySpan) {
        std::cout << element << " ";
    }
    return 0;
}

Explanation: Here, std::span<int> mySpan(data); creates a span covering all elements in the data array. This example illustrates how std::span provides an iterable view without requiring array copies​

2. Constructing a std::span from an std::vector

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

int main() {
    std::vector<int> vec = {10, 20, 30, 40, 50};
    
    // Create a span over the vector
    std::span<int> spanFromVector(vec);

    // Modify elements via span
    spanFromVector[0] = 100;

    // Display modified vector
    for (int element : vec) {
        std::cout << element << " ";
    }
    return 0;
}

Explanation: When we create a span from vec, changes made through spanFromVector reflect in vec, since std::span is simply a view. This capability makes std::span useful for functions that need read or write access to data without taking ownership​

3. Using Class Template Argument Deduction (CTAD) for Simplicity

Starting in C++17, we can omit the type arguments when creating a std::span, letting the compiler deduce them automatically:

#include <span>
#include <iostream>

int main() {
    int data[] = {7, 8, 9, 10};
    
    // Deduction of type with CTAD
    std::span spanAutoDeduct(data);
    
    for (int value : spanAutoDeduct) {
        std::cout << value << " ";
    }
    return 0;
}

In this example, the compiler deduces std::span<int> for spanAutoDeduct based on the type of data.

std::span simplifies code and improves safety by offering a bounds-safe view over contiguous data without owning it. It provides a way to efficiently pass data to functions without copying, making it a valuable addition to the C++20 standard library. For functions that operate on arrays, vectors, or raw memory, std::span can greatly simplify function interfaces and improve code readability.

Common Use Cases for std::span

std::span is designed to improve the flexibility and safety of working with contiguous memory in C++. Its non-owning, lightweight structure allows it to act as a “view” into data without managing the memory itself. This makes it ideal for scenarios where you need safe access to data segments without copying or modifying the underlying structure. Here are some common use cases that demonstrate the versatility of std::span in real-world programming:

1. Passing Arrays to Functions

When functions need to operate on arrays or vectors, it’s often beneficial to avoid copying data or worrying about the array size. std::span allows functions to accept data from multiple container types without changing the function signature. Here’s an example of how std::span simplifies function parameters:

#include <span>
#include <iostream>

void printValues(std::span<const int> values) {
    for (int value : values) {
        std::cout << value << " ";
    }
    std::cout << "\n";
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    printValues(arr); // Passes an array as a span

    std::vector<int> vec = {6, 7, 8, 9, 10};
    printValues(vec); // Passes a vector as a span
    return 0;
}

Explanation: The printValues function accepts a std::span<const int>, making it compatible with any contiguous data type, whether it’s a raw array, std::vector, or even std::array. This approach simplifies code, improves readability, and prevents accidental modifications to the original data​.

2. Creating Partial Views of Data

In cases where you only need to operate on a portion of an array or vector, std::span allows for the creation of subviews, known as subspans. This is especially useful when working with large datasets or splitting data into manageable chunks.

#include <span>
#include <iostream>

void processPartialData(std::span<const int> partialData) {
    for (int value : partialData) {
        std::cout << value << " ";
    }
    std::cout << "\n";
}

int main() {
    int data[] = {1, 2, 3, 4, 5, 6, 7, 8};
    std::span<int> fullSpan(data); // Span over the full array

    // Creating subviews with only part of the data
    processPartialData(fullSpan.subspan(2, 4)); // View of {3, 4, 5, 6}
    return 0;
}

Explanation: The processPartialData function operates only on a subspan of the data array, reducing memory usage and simplifying data management. This kind of flexibility makes std::span useful for data processing applications or any situation where working with specific data segments is beneficial.

3. Efficiently Passing Data for Read or Write Access

For functions that require read-only access to data, std::span<const T> can enforce const-correctness, making it clear that data won’t be modified. Conversely, std::span<T> allows read-write access. This distinction ensures code clarity and helps prevent unintended modifications, enhancing both security and maintainability.

#include <span>
#include <iostream>

void displayData(std::span<const int> data) {
    for (int value : data) {
        std::cout << value << " ";
    }
    std::cout << "\n";
}

void incrementData(std::span<int> data) {
    for (int& value : data) {
        value += 1;
    }
}

int main() {
    int numbers[] = {10, 20, 30};

    displayData(numbers);   // Only displays data, no modifications
    incrementData(numbers); // Modifies data in-place

    displayData(numbers);   // Display modified data
    return 0;
}

Explanation: By using std::span<const int> in displayData and std::span<int> in incrementData, we make it clear which functions can modify the data and which cannot. This not only enhances readability but also prevents accidental data changes, making std::span suitable for a variety of function interfaces.

These examples illustrate how std::span provides a versatile, type-safe approach to passing and managing data in C++. The ability to pass views rather than entire containers reduces overhead and enhances code clarity, making std::span a valuable tool in the C++20 standard library.

Basic Syntax and Construction of std::span

To use std::span, you start by creating a span over an existing array, vector, or other contiguous container. Since std::span is a lightweight, non-owning view, it can be constructed directly from various data sources without taking ownership. This section covers several ways to construct std::span from common data structures in C++.

1. Constructing a std::span from a C-style Array

Creating a span from a raw C-style array is straightforward. You simply pass the array to the std::span constructor, and the span automatically determines its size.

#include <span>
#include <iostream>

int main() {
    int data[] = {1, 2, 3, 4, 5};
    
    // Create a span covering the entire array
    std::span<int> mySpan(data);

    // Access elements
    for (int element : mySpan) {
        std::cout << element << " ";
    }
    return 0;
}

Explanation: In this example, std::span<int> mySpan(data); creates a span that includes all elements in the data array. This is possible because std::span detects the size of raw arrays automatically, making it easier to use with standard arrays​.

2. Constructing a std::span from an std::vector

If you’re working with std::vector, you can create a span over the vector’s contents by simply passing the vector to the span’s constructor.

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

int main() {
    std::vector<int> vec = {10, 20, 30, 40, 50};
    
    // Create a span covering the entire vector
    std::span<int> spanFromVector(vec);

    // Modify elements via span
    spanFromVector[0] = 100;

    // Display modified vector
    for (int element : vec) {
        std::cout << element << " ";
    }
    return 0;
}

Explanation: Here, the spanFromVector allows you to interact with vec as if it were a raw array. Any changes made through the span directly affect the vector. This functionality can simplify function interfaces where you may want to provide read and write access to a vector without copying it​.

3. Constructing a std::span from std::array

Creating a span from an std::array is similar to working with std::vector. Since std::array has a fixed size, std::span can easily wrap around it and offer similar flexibility.

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

int main() {
    std::array<int, 5> arr = {5, 10, 15, 20, 25};

    // Create a span covering the entire std::array
    std::span<int> spanFromArray(arr);

    // Display elements
    for (int element : spanFromArray) {
        std::cout << element << " ";
    }
    return 0;
}

Explanation: In this case, spanFromArray wraps around the fixed-size std::array and provides the same easy access to its elements. std::span works well with std::array since they both assume contiguous memory, making them naturally compatible​.

4. Using Class Template Argument Deduction (CTAD)

C++17 introduced Class Template Argument Deduction (CTAD), which enables you to omit the type and size arguments when constructing a std::span. This allows the compiler to deduce these types based on the arguments provided, making the syntax cleaner.

#include <span>
#include <iostream>

int main() {
    int data[] = {7, 8, 9, 10};
    
    // Deduction of type and size with CTAD
    std::span spanAutoDeduct(data);
    
    for (int value : spanAutoDeduct) {
        std::cout << value << " ";
    }
    return 0;
}

Explanation: In this example, the compiler deduces std::span<int> for spanAutoDeduct based on the type of data. This feature makes std::span even easier to use by removing the need to specify template parameters explicitly, which can be especially helpful for more complex types.

Our Introduction to std::span

In C++20, std::span has emerged as a powerful feature for managing access to contiguous data efficiently and safely. Its non-owning, lightweight structure allows it to be used with various data sources—like arrays, vectors, and std::array—making it a versatile tool for passing data to functions without needing to copy or resize it.

We explored how std::span works with C-style arrays, std::vector, and std::array, providing a unified way to view data and simplifying function interfaces. By enabling code to pass arrays, vectors, or subviews safely and easily, std::span enhances type safety, encourages const-correctness, and minimizes memory management concerns.

With std::span, C++ code becomes cleaner and more efficient, aligning with modern development needs for performance, safety, and simplicity. As you adopt C++20 features, std::span offers a compelling way to streamline data access and improve the reliability of your code.

Keep reading:


Discover more from John Farrier

Subscribe to get the latest posts sent to your email.

Leave a Reply

Discover more from John Farrier

Subscribe now to keep reading and get access to the full archive.

Continue reading