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::vector
s, or std::array
s. 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, orstd::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.