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.


2 Comments so far »

  1. 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?

  2. 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.

Comment RSS · TrackBack URI

Leave a Comment

Name: (Required)

E-mail: (Required)