C++20: std::span – A View on a Continuous Sequence of Data
std::span, defined in <span>, allows you to handle a sequence of continuous data, without having to worry about where the data is actually stored. For example, suppose you have a function to print the elements of a std::vector:
void print(const std::vector<int>& values)
{
for (const auto& value : values) { std::cout << value << " "; }
std::cout << std::endl;
}
This function requires as argument a reference to a std::vector<int>. If you also want to print elements of a C-style array, then you can add a second overload of the print() function, for example:
void print(const int values[], size_t count)
{
for (size_t i{ 0 }; i < count; ++i) { std::cout << values[i] << " "; }
std::cout << std::endl;
}
With these two overloads, you can call your print() function with either a reference to a std::vector<int> or with a C-style array. If you want to support other containers, then you can add even more overloads. With std::span, it is possible to write a single function that can work with all kinds of sequential data. Instead of the previous two overloads, you can just write the following single function:
void print(std::span<const int> values)
{
for (const auto& value : values) { std::cout << value << " "; }
std::cout << std::endl;
}
Note that a span basically just contains a pointer to the first element in the sequence and the number of elements in the sequence, and never copies the underlying data. Hence, a span is very cheap to copy and is usually passed by value, just as std::string_view.
This single print() function accepting a std::span can be called for continuous data stored in std::vectors, std::arrays, C-style arrays, and more. Here are some examples:
std::vector v{ 1, 2, 3 };
print(v); // Pass a vector.
std::array a{ 4, 5, 6, 7 };
print(a); // Pass a std::array.
print({ a.data() + 1, 2 }); // Pass part of a std::array.
int ca[]{ 8, 9, 10 };
print(ca); // Pass a C-style array.
std::span s{ v }; // Construct a span from a vector.
print(s); // Pass a std::span.
print(s.subspan(1, 2)); // Pass part of a std::span.
The output of this code snippet is as follows:
1 2 3
4 5 6 7
5 6
8 9 10
1 2 3
2 3
Tip: If you write a function accepting a const vector<T>&, I recommend considering to accept a span<const T> instead. This allows your function to work with all kinds of continuous data, independent of where the data is actually stored.
My book, Professional C++, 5th Edition, explains all new C++20 features, and much more.
Denis Gladkiy said,
Wrote on January 25, 2022 @ 5:03 am
This example does not impress. Almost all of the stuff could be done with pre-C++20 templates. Even overload for arrays could be done via templates so one does not require to pass array size as a parameter (which is error-prone).
So why do I want to use spans?
Marc Gregoire said,
Wrote on February 3, 2022 @ 1:53 pm
I think using std::span makes it more readable and more maintainable compared to using templates for this.
On top of that, you only need 1 function. You don’t need to write special overloads for arrays for example.