C++ Beyond the Syllabus #10: C++20 std::span

Efficient, non-owning contiguous memory access.

Jared Miller
5 min readAug 15, 2024

Not a Medium member? View this entire article here!

This article is a part of the C++ Beyond the Syllabus series. Subscribe here to receive each new issue directly in your inbox.

Introduced in C++20, std::span provides a lightweight, non-owning, and modifiable reference to contiguously stored data.

The use case is simple:

std::span can drastically simplify code by acting as a uniform interface to operate on different representations of contiguous sequences of elements.

For example, you can write a single function to operate on a C-style array, std::array, std::vector, and std::string.

How’s It Work?

std::span is actually very similar to std::string_view (with a few differences).

Like `std::string_view`

Discussed in this article, std::string_view is optimized for read-only string operations. It can only be used on char arrays and does not allow the programmer to directly modify the underlying data.

std::span is a little different. It’s a template, so it can be used with any data type, not just chars. std::span also allows for modification of the underlying data.

Both std::string_view and std::span provide non-owning references, so you’ll need to be sure std::span doesn’t outlive the underlying data.

They are actually implemented very similarly as well. Just like std::string_view, std::span consists of a pointer to the first item and the size of the sequence.

Constructing `std::span`

The constructors are pretty versatile. You can construct a std::span

  • from a C-style array
int c_arr[] = { 1, 2, 3};
std::span<int> c_arr_span( c_arr );
  • directly from std::array or std::vector
std::vector<char> vec = { 'a', 'b', 'c' };
std::span<char> vec_span( vec );
  • with iterators to the front and end of a sequence
std::array<double, 3> arr = { 0.1, 0.2, 0.3 };
std::span<double> arr_span( arr.begin(), arr.end() );
  • with a pointer to the first element and the size
char* c_str = new char[]{ 'h', 'e', 'l', 'l', 'o', '\n'};
std::span<char> c_str_span( c_str, strlen(c_str) );
// ...
delete[] c_str; // Note: Usage of `c_str_span` after this
// line would access undefined memory!

Here’s a Compiler Explorer link with the above examples.

Accessing Elements

Just like a std::vector or array, you can access elements in a std::span via the bracket operator ([]) for unchecked access. This means if you access an index greater than the size of the span (or the underlying data), you’ll get undefined behavior.

In C++26, support will be added for the std::span::at function, which will include a bounds check for further safety, just like std::vector::at. Like its std::vector counterpart, this check would really just cause unnecessary overhead if your surrounding code already ensures only valid indexes will be accessed.

Here’s a snippet showing how each of these accesses might be used:

#include <span>
#include <iostream>

int main()
{
std::array<int, 3> arr = { 1, 2, 3 };
std::span<int> s( arr );

for ( size_t i = 0; i < s.size(); ++i )
{
std::cout << s[ i ] << " "; // unchecked access
}
std::cout << std::endl;

try
{
std::cout << s.at( 3 ) << std::endl; // bounds-checked access
}
catch ( const std::out_of_range& /*e*/ )
{
std::cout << "Out of range!" << std::endl;
}
}

Subspans

Like std::string’s substring functionality, std::span provides subspan functionality. This can be especially useful when implementing algorithms where you may need to traverse small portions of a sequence.

#include <span>
#include <iostream>

int main()
{
std::array<int, 5> arr = { 1, 2, 3, 4, 5 };
std::span<int> arr_span( arr );

// create subspan of size 3 beginning at index 1
std::span<int> subspan = arr_span.subspan( 1, 3 );

for ( const auto i: subspan )
{
std::cout << i << " ";
}
}

This would output:

2 3 4

You can also use the std::span::subspan::first(x) or std::span::subspan::last(x) helper functions to retrieve subspans of the first or last x elements.

Some Other Properties

Similar to STL containers, std::span provides (or will soon provide in C++23,26) many member functions to help operate on the underlying data. Though, note that a std::span itself is not a container because it doesn’t really own anything!

You can access the underlying data directly via the std::span::data function. Beginning with C++26, iterators will be supported for std::span, which will allow them to be used with a variety of STL algorithms, like std::sort, std::binary_search, bounds functions, and many more.

Just like all other C++ types, you can create const spans, span references, and span pointers as well.

Multi-Dimensional Spans

These aren’t likely to be used as frequently as std::span for most C++ programmers, but it’s worth noting that std::mdspan allows you to view, access, and operate on contiguous sequences of memory as if it were multi dimensional. I’m not going to dive deep into this today, but it’s good to know that this exists.

Disclaimers

std::span is non-owning. The side effects of this can lead to undefined behavior and unexpected crashes if you are not careful. I provided an in-depth overview of this disclaimer in my std::string_view article, but here are the (much abbreviated) takeaways:

  • You must make sure the underlying data outlives all std::spans created upon that data.
  • Unless you are very careful, you probably want to avoid std::span in multi-threaded environments.
  • Since growth beyond the current capacity of any std::string or std::vector will lead to reallocation and copying the underlying data to a different place in memory, any append operations on those objects should be expected to invalidate any std::spans referencing their underlying data.

Once a std::span is created, it cannot be resized. You can always create a subspan of smaller size, but you will not be able to safely increase the size.

The Main Use Case

Large C++ applications often include various libraries, input sources, and other logic, which employ several different representations of contiguous data. You can simplify libraries and consolidate code by using std::span to uniformly view and operate on any of those representations.

What’s Next?

If you liked this article, please consider clapping and following!

Subscribe here to receive weekly-ish new issues of C++ Beyond the Syllabus directly in your inbox.

Sources & More Info

--

--

Jared Miller
Jared Miller

Written by Jared Miller

A C++ Software Engineer in high frequency trading. Excited about low-latency code, distributed systems, and education technology.

No responses yet