What is a "span" and when should I use one?
Recently I've gotten suggestions to use span<T>
's in my code, or have seen some answers here on the site which use span
's - supposedly some kind of container. But - I can't find anything like that in the C++17 standard library.
So what is this mysterious span<T>
, and why (or when) is it a good idea to use it if it's non-standard?
What is it?
A span<T>
is:
- A very lightweight abstraction of a contiguous sequence of values of type
T
somewhere in memory. - Basically a
struct { T * ptr; std::size_t length; }
with a bunch of convenience methods. - A non-owning type (i.e. a "reference-type" rather than a "value type"): It never allocates nor deallocates anything and does not keep smart pointers alive.
It was formerly known as an array_view
and even earlier as array_ref
.
When should I use it?
First, when not to use it:
- Don't use it in code that could just take any pair of start & end iterators, like
std::sort
,std::find_if
,std::copy
and all of those super-generic templated functions. - Don't use it if you have a standard library container (or a Boost container etc.) which you know is the right fit for your code. It's not intended to supplant any of them.
Now for when to actually use it:
Use
span<T>
(respectively,span<const T>
) instead of a free-standingT*
(respectivelyconst T*
) when the allocated length or size also matter. So, replace functions like:void read_into(int* buffer, size_t buffer_size);
with:
void read_into(span<int> buffer);
Why should I use it? Why is it a good thing?
Oh, spans are awesome! Using a span
...
-
means that you can work with that pointer+length / start+end pointer combination like you would with a fancy, pimped-out standard library container, e.g.:
for (auto& x : my_span) { /* do stuff */ }
std::find_if(my_span.cbegin(), my_span.cend(), some_predicate);
-
std::ranges::find_if(my_span, some_predicate);
(in C++20)
... but with absolutely none of the overhead most container classes incur.
-
lets the compiler do more work for you sometimes. For example, this:
int buffer[BUFFER_SIZE]; read_into(buffer, BUFFER_SIZE);
becomes this:
int buffer[BUFFER_SIZE]; read_into(buffer);
... which will do what you would want it to do. See also Guideline P.5.
-
is the reasonable alternative to passing
const vector<T>&
to functions when you expect your data to be contiguous in memory. No more getting scolded by high-and-mighty C++ gurus! -
facilitates static analysis, so the compiler might be able to help you catch silly bugs.
-
allows for debug-compilation instrumentation for runtime bounds-checking (i.e.
span
's methods will have some bounds-checking code within#ifndef NDEBUG
...#endif
) -
indicates that your code (that's using the span) doesn't own the pointed-to memory.
There's even more motivation for using span
s, which you could find in the C++ core guidelines - but you catch the drift.
But is it in the standard library?
edit: Yes, std::span
was added to C++ with the C++20 version of the language!
Why only in C++20? Well, While the idea is not new - its current form was conceived in conjunction with the C++ core guidelines project, which only started taking shape in 2015. So it took a while.
So how do I use it if I'm writing C++17 or earlier?
It's part of the Core Guidelines's Support Library (GSL). Implementations:
- Microsoft / Neil Macintosh's GSL contains a standalone implementation:
gsl/span
-
GSL-Lite is a single-header implementation of the whole GSL (it's not that big, don't worry), including
span<T>
.
The GSL implementation does generally assume a platform that implements C++14 support [11]. These alternative single-header implementations do not depend on GSL facilities:
-
martinmoene/span-lite
requires C++98 or later -
tcbrindle/span
requires C++11 or later
Note that these different span implementations have some differences in what methods/support functions they come with; and they may also differ somewhat from the version adopted into the standard library in C++20.
Further reading: You can find all the details and design considerations in the final official proposal before C++17, P0122R7: span: bounds-safe views for sequences of objects by Neal Macintosh and Stephan J. Lavavej. It's a bit long though. Also, in C++20, the span comparison semantics changed (following this short paper by Tony van Eerd).
A span<T>
is this:
template <typename T>
struct span
{
T * ptr_to_array; // pointer to a contiguous C-style array of data
// (which memory is NOT allocated or deallocated
// by the span)
std::size_t length; // number of elements of type `T` in the array
// Plus a bunch of constructors and convenience accessor methods here
}
It is a light-weight wrapper around a C-style array, preferred by C++ developers whenever they are using C libraries and want to wrap them with a C++-style data container for "type safety" and "C++-ishness" and "feelgoodery". :)
Note: I call the struct container defined above, known as a span, a "light-weight wrapper around a C-style array" because it points to a contiguous piece of memory, such as a C-style array, and wraps it with accessor methods and the array's size. This is what I mean by "light-weight wrapper": it is a wrapper around a pointer and a length variable, plus functions.
Going further:
@einpoklum does a pretty good job of introducing what a span
is in his answer here. However, even after reading his answer, it is easy for someone new to spans to still have a sequence of stream-of-thought questions which aren't fully answered, such as the following:
- How is a
span
different from a C array? Why not just use one of those? It seems like it's just one of those with the size known as well... - Wait, that sounds like a
std::array
, how is aspan
different from that? - Oh, that reminds me, isn't a
std::vector
like astd::array
too? - I'm so confused. :( What's a
span
?
So, here's some additional clarity on that:
DIRECT QUOTE OF HIS ANSWER--WITH MY ADDITIONS and parenthetical comments IN BOLD and my emphasis in italics:
What is it?
A
span<T>
is:
- A very lightweight abstraction of a contiguous sequence of values of type
T
somewhere in memory.- Basically a single struct
{ T * ptr; std::size_t length; }
with a bunch of convenience methods. (Notice this is distinctly different fromstd::array<>
because aspan
enables convenience accessor methods, comparable tostd::array
, via a pointer to typeT
and length (number of elements) of typeT
, whereasstd::array
is an actual container which holds one or more values of typeT
.)- A non-owning type (i.e. a "reference-type" rather than a "value type"): It never allocates nor deallocates anything and does not keep smart pointers alive.
It was formerly known as an
array_view
and even earlier asarray_ref
.
Those bold parts are critical to one's understanding, so don't miss them or misread them! A span
is NOT a C-array of structs, nor is it a struct of a C-array of type T
plus the length of the array (this would be essentially what the std::array
container is), NOR is it a C-array of structs of pointers to type T
plus the length, but rather it is a single struct containing one single pointer to type T
, and the length, which is the number of elements (of type T
) in the contiguous memory block that the pointer to type T
points to! In this way, the only overhead you've added by using a span
are the variables to store the pointer and length, and any convenience accessor functions you use which the span
provides.
This is UNLIKE a std::array<>
because the std::array<>
actually allocates memory for the entire contiguous block, and it is UNLIKE std::vector<>
because a std::vector
is basically just a std::array
that also does dynamic growing (usually doubling in size) each time it fills up and you try to add something else to it. A std::array
is fixed in size, and a span
doesn't even manage the memory of the block it points to, it just points to the block of memory, knows how long the block of memory is, knows what data type is in a C-array in the memory, and provides convenience accessor functions to work with the elements in that contiguous memory.
It is part of the C++ standard:
std::span
is part of the C++ standard as of C++20. You can read its documentation here: https://en.cppreference.com/w/cpp/container/span. To see how to use Google's absl::Span<T>(array, length)
in C++11 or later today, see below.
Summary Descriptions, and Key References:
-
std::span<T, Extent>
(Extent
= "the number of elements in the sequence, orstd::dynamic_extent
if dynamic". A span just points to memory and makes it easy to access, but does NOT manage it!): - https://en.cppreference.com/w/cpp/container/span
-
std::array<T, N>
(notice it has a fixed sizeN
!): - https://en.cppreference.com/w/cpp/container/array
- http://www.cplusplus.com/reference/array/array/
-
std::vector<T>
(automatically dynamically grows in size as necessary): - https://en.cppreference.com/w/cpp/container/vector
- http://www.cplusplus.com/reference/vector/vector/
How Can I Use span
in C++11 or later today?
Google has open-sourced their internal C++11 libraries in the form of their "Abseil" library. This library is intended to provide C++14 to C++20 and beyond features which work in C++11 and later, so that you can use tomorrow's features, today. They say:
Compatibility with the C++ Standard
Google has developed many abstractions that either match or closely match features incorporated into C++14, C++17, and beyond. Using the Abseil versions of these abstractions allows you to access these features now, even if your code is not yet ready for life in a post C++11 world.
Here are some key resources and links:
- Main site: https://abseil.io/
- https://abseil.io/docs/cpp/
- GitHub repository: https://github.com/abseil/abseil-cpp
-
span.h
header, andabsl::Span<T>(array, length)
template class: https://github.com/abseil/abseil-cpp/blob/master/absl/types/span.h#L153
Other references:
- Struct with template variables in C++
- Wikipedia: C++ classes
- default visibility of C++ class/struct members
Related:
- [another one of my answers on templates and spans] How to make span of spans