If you’ve been working with arrays or vectors in C++, you’ve probably run into situations where you just want a view of some data, without copying it around. That’s exactly what std::span is for. It’s part of C++20 and lives in the header.
The benefits of std::span
Utilizing std::span offers a safer and more expressive alternative to traditional pointers and loops by encapsulating size information alongside the data, thereby reducing the risk of buffer overflows and enhancing code clarity. It simplifies range-based operations, enabling more concise and maintainable code without the need for manual size management or raw pointer arithmetic.
Consider the traditional C or C++ way of processing a range of data versus std::span approach:
void handle_buffer(int* data, std::size_t length) {
for (std::size_t index = 0; index < length; ++index) {
// do something with data[index]
}
}
With std::span we can write:
void handle_buffer(std::span<int> values) {
for (auto& value : values) {
// do something with value
}
}
When working with contiguous data in modern C++, std::span offers a much cleaner and safer alternative to the traditional “pointer + size” pattern. It provides a lightweight view into existing memory without copying, while also making your function interfaces clearer and less error‑prone.
Runtime benefits of std::span:
- Zero‑overhead abstraction: a span is just a pointer plus a size.
- No copying or allocation: it provides a lightweight view into existing data.
- Works seamlessly with arrays,
std::vector,std::array, and other contiguous ranges.
Safety benefits of std::span:
- Always carries its size, reducing off‑by‑one and mismatched‑length bugs.
- Makes function intent explicit: “this is a contiguous sequence.”
- C++26 adds
span::at()for bounds‑checked access when needed.
Example usecases of std::span
std::span lives in its own header <span>. Its basic declaration looks like this:
template<class T, std::size_t Extent = std::dynamic_extent>
class span;
Here:
Tis the type of elements you want to view.Extentis the number of elements. If you don’t know it at compile-time, you can usestd::dynamic_extent.
There are two main ways to use std::span: static extent and dynamic extent. Let’s break them down.
Static Extent
In this example, we’ll see how to create a span with a fixed number of elements from an array. You’ll learn what happens if the array size doesn’t match the span size.
int scores[] = {95, 85, 75, 65, 55};
std::span<int, 3> top_scores {scores}; // take the first 3 elements
- The span is statically sized to 3.
- It only “sees” the first 3 elements of the array.
- If you try a different size, the compiler throws an error.
- Use static extent when you know exactly how many elements you want to work with.
Think of this as a strictly sized window into your array. you can’t resize it or use it with a different-length array.
Dynamic Extent
Here we’ll create a span where the size is determined at runtime. This is useful for vectors or arrays when the length isn’t fixed at compile-time.
int data_array[] = {100, 200, 300, 400, 500};
std::vector<int> data_vector {100, 200, 300, 400, 500};
std::span<int> array_span {data_array}; // works with arrays
std::span<int> vector_span {data_vector}; // works with vectors too!
The span automatically adapts to the size of the vector.
Dynamic extent is perfect for containers that might change size or when the size isn’t known in advance.
- The span automatically adapts to the size of the vector.
- Dynamic extent is perfect for containers that might change size or when the size isn’t known in advance.
- You can safely pass this span to functions without worrying about the size.
Using std::span in a function
Next, we’ll see how to write a single function that works for arrays or vectors. This demonstrates how spans can simplify code and avoid copying data.
void double_values(std::span<int> numbers) {
for (auto& n : numbers) {
n *= 2;
}
}
int main() {
int values[] = {1, 2, 3, 4};
std::vector<int> more_values {5, 6, 7, 8};
double_values(values); // works with array
double_values(more_values); // works with vector
}
- Dynamic extent allows the same function to handle both arrays and vectors.
- The span is a view into the original data, so modifications affect the original container.
- No need for overloads or copying elements.
Span over part of a std::vector
Finally, we’ll explore how to create a subspan, i.e., a span that points to only part of a container. This is helpful for processing slices of data without copying.
std::vector<int> data {10, 20, 30, 40, 50};
std::span<int> middle_data {data.begin() + 1, 3}; // span of 3 elements starting from second
- The span points to
{20, 30, 40}, a subsection of the vector. - Subspans allow you to focus on a part of a container without copying it.
- Useful for algorithms that need to operate on a segment of data.
