Let's try C++20 | std::span
Tags: cpp, tutorial, programming, todayilearned
Published: 2020-10-28 07:38:56

Eventually, I have decided to try C++20 😀

Let's start with `std::span`!

## What is a Span?

Here is the definition given by [cppreference](https://en.cppreference.com/w/cpp/container/span):

> The class template `span` describes an object that can refer to a contiguous sequence of objects with the first element of the sequence at position zero.

Spans are sometimes called "views" because they don't own the sequence of objects.

If you are familiar `std::string_view` from C++17, then you can easily understand the concept of spans. `std::string_view`s are somehow spans on sequences of characters. The main difference is that spans can modify the objects while string views can't modify the caracters.

In the following, I will try to use `std::span`s in various situations to understand how they work and how they can be useful for future code.

## With Plain Arrays

I personally use a lot of plain arrays because I work with on embedded systems where those C-style arrays are widely used. Why? Because dynamic allocation is forbidden (so `std::vector`s are not an option) and because there is a lot of C code.

When you pass a plain array to a function, it is decayed to a pointer to its first element and its size is lost.
`std::span`s sound like a great solution to safely pass around plan arrays to functions.

First, let's write a template function that prints any span:

```cpp
#include <iostream>
#include <span>

template<typename T, std::size_t length>
void print(std::span<T, length> span) {
    for(auto i : span) {
        std::cout << i << ' ';
    }
    std::cout << '\n';
}
```

Then, let's create a span from a plain array:

```cpp
int main() {
    int data[] = {1, 2, 3, 4, 5};
    print(std::span{data});
}

```

This code outputs:

```
1 2 3 4 5
```

The size of the array is deduced from its type and stored in the `std::span` object.

## With a Pointer and a Size

If you are already in a function where the original plain array has been "transformed" to pointer + size, you can create a span from them. Of course, you cannot be sure it maps the original plain array:

```cpp
void function(int* pointer, unsigned int size) {
    printf("> ");
    print(std::span{pointer, size});
}

int main() {
    int data[] = {1, 2, 3, 4, 5};

    function(data, 0);
    function(data, 2);
    function(data, 5);
    function(data, 10);
}
```

Output:

```
> 
> 1 2 
> 1 2 3 4 5 
> 1 2 3 4 5 32766 0 0 4199488 0
```

The last span is reading outside of the array, which is clearly an undefined behavior. You are responsible for creating the span with a valid sequence.

The form (2) of [`std::span`'s constructors](https://en.cppreference.com/w/cpp/container/span/span) is used here:

```cpp
template< class It >
explicit(extent != std::dynamic_extent)
constexpr span( It first, size_type count );
```

## With `std::array`s or `std::vector`s

You can also easily create spans from `std::array`s or `std::vector`s:

```cpp
int main() {
    std::array a = {1, 2, 3, 4, 5};
    std::vector v = {6, 7, 8, 9, 10};

    print(std::span{a});
    print(std::span{v});
}
```

Output:

```
1 2 3 4 5
6 7 8 9 10
```

Obviously, constructor (5) is used for the array:

```cpp
template< class U, std::size_t N >
constexpr span( std::array<U, N>& arr ) noexcept;
```

But which one is used for the vector?

It took me a while to understand that form (7) is used:

```cpp
template< class R >
explicit(extent != std::dynamic_extent)
constexpr span( R&& r );
```

This is because a vector is a [range](https://en.cppreference.com/w/cpp/ranges/range) :

```cpp
#include <iostream>
#include <span>
#include <ranges>
#include <vector>

int main() {
    std::vector v = {1, 2, 3, 4, 5};
    auto r = std::ranges::range<decltype(v)>;
    std::cout << std::boolalpha << r;
}
```

[`std::ranges::range`](https://en.cppreference.com/w/cpp/ranges/range) is a concept, a new feature of C++20. Concepts are one of the next features I will try.

## With a part of an `std::array` or `std::vector`

You can't directly create a span over a part of the array or the vector. For instance:

```cpp
int main() {
    std::array a = {1, 2, 3, 4, 5};
    print(std::span{a, 2});
}
```

doesn't compile because of the following error:

> `error: no viable constructor or deduction guide for deduction of template arguments of 'span'`

The same error occurs with an `std::vector`.

The trick is to get an iterator/pointer from the container. For instance:

```cpp
int main() {
    std::array a = {1, 2, 3, 4, 5};

    print(std::span{a.data(), 2});
    print(std::span{a.cbegin() + 2, a.cbegin() + a.size()});
}}
```

Output:

```
1 2
3 4 5
```

Here, constructors (2) and (3) are used:

```cpp
// (2)
template< class It >
explicit(extent != std::dynamic_extent)
constexpr span( It first, size_type count );

// (3)	
template< class It, class End >
explicit(extent != std::dynamic_extent)
constexpr span( It first, End last );
```

## Subspans

It is possible to create subspans, which are spans on a part of a span. There are 3 member functions to do that:

1. [`first()`](https://en.cppreference.com/w/cpp/container/span/first): obtains a subspan consisting of the first N elements of the sequence
2. [`last()`](https://en.cppreference.com/w/cpp/container/span/last): obtains a subspan consisting of the last N elements of the sequence
3. [`subspan()`](https://en.cppreference.com/w/cpp/container/span/subspan): obtains a subspan

Example:

```cpp
int main() {
    std::array data = {1, 2, 3, 4, 5};

    auto span = std::span{data};
    print(span);

    print(span.first(3));
    print(span.last(3));
    print(span.subspan(1, 3));
}
```

Output:

```
1 2 3 4 5 
1 2 3 
3 4 5 
2 3 4 
```

## Modify Objects in the Span

It is possible to modify the objects the span refers to:

```cpp
template<typename T, std::size_t length>
void change(std::span<T, length> span) {
    std::transform(span.begin(), span.end(), span.begin(), std::negate());
}

int main() {
    std::array data = {1,2,3,4,5};

    print(std::span{data});
    change(std::span{data});
    print(std::span{data});
}
```

Output:

```
1 2 3 4 5 
-1 -2 -3 -4 -5 
```

Note than `std::span` doesn't have `cbegin()` and `cend()` as iterator functions. It only has `begin()` / `end()` / `rbegin()` / `rend()`.

Nevertheless, if we declare `data` as `const std::array data = {1,2,3,4,5};`, then the code won't compile. Here is an example where we try to modify a `const` array using a span:

```cpp
int main() {
    const std::array data = {1,2,3,4,5};
    auto s = std::span{data};
    s[0] = 42;
}
```

This doesn't compile:

> `error: assignment of read-only location 's.std::span<const int, 5>::operator[](0)'`

## Static vs Dynamic Extent

The size of the sequence of objects can be static or dynamic, [as stated in the overview of the class](https://en.cppreference.com/w/cpp/container/span):

> A span can either have a static extent, in which case the number of elements in the sequence is known at compile-time and encoded in the type, or a dynamic extent.
>
> If a span has dynamic extent a typical implementation holds two members: a pointer to T and a size. A span with static extent may have only one member: a pointer to T.

Let's create a function to print the extent of spans:

```cpp
template<typename T, std::size_t length>
void print_extent(std::span<T, length> span) {
    std::cout << std::hex << "0x" << span.extent << '\n';
}

int main() {
    std::array array = {1,2,3,4,5};

    auto span = std::span{array};
    print_extent(span);

    print_extent(span.subspan(1, 3));

    std::vector<int> vector;
    print_extent(std::span{vector});

    int c_array[] = {42};
    print_extent(std::span{c_array});
}
```

Output:

```
0x5
0xffffffffffffffff
0xffffffffffffffff
0x1
```

Those `0xffffffffffffffff` do look like [`std::dynamic_extent`](https://en.cppreference.com/w/cpp/container/span/dynamic_extent).

A dynamic extent really means that the size of the span changes along with the size of the sequence. Here is an example where we push items to a vector and look how the size of the span evolves (we know from the previous example it has dynamic extent):

```cpp
template<typename T, std::size_t length>
void print(std::span<T, length> span) {
    std::cout << "Size = " << span.size() << " --> ";

    for(auto i : span) {
        std::cout << i << ' ';
    }

    std::cout << '\n';
}

int main() {
    std::vector<int> vector;
    print(std::span{vector});

    vector.push_back(42);
    print(std::span{vector});

    vector.push_back(66);
    print(std::span{vector});
}
```

Output:

```
Size = 0 --> 
Size = 1 --> 42 
Size = 2 --> 42 66 
```

## Non-Template Code with Dynamic Extent

A span with a static extent can be converted implicitly to span with a dynamic extent:

```cpp
#include <iostream>
#include <span>

int main() {
    char buffer[] = "hello, world";

    std::span<char, 13> static_span{buffer};
    std::span<char> dynamic_span{static_span};

    std::cout << std::boolalpha;

    std::cout << (static_span.extent == std::dynamic_extent) <<  '\n';
    std::cout << (dynamic_span.extent == std::dynamic_extent) <<  '\n';
}
```

Output:

```
false
true
```

Why is this really interesting? Because we can rely on this conversion to write non-template functions that take spans of any size as their parameters.
This is something we cannot do with `std::array`s.
Compare these 2 functions that prints arrays:

```cpp
#include <array>
#include <iostream>
#include <span>

void print(std::span<char> span) {
   for(auto c : span) std::cout << c;
   std::cout << '\n';
}

template<std::size_t size>
void print(std::array<char, size> array) {
   for(auto c : array) std::cout << c;
   std::cout << '\n';
}

int main() {
    std::array data{'h', 'e', 'l', 'l', 'o'};
    print(data);
    print(data);
}
```

Both of course output "hello". Because the first one is non-template, it can be simply declared in a \*.h and defined in a \*.c.
This is not possible of course for the second one.
But I think you already know that "issue" with templates, right? 😅

## sizeof()

And finally, because I am an embedded software developer and I care about footprint, let's take a look at the size of a span:

```cpp
int main() {
    std::vector<int> vector;
    auto sv = std::span{vector};
    std::cout << sizeof(sv) << '\n';

    std::array<int, 10> array;
    auto sa = std::span{array};
    std::cout << sizeof(sa) << '\n';

    int c_array[10];
    auto sca = std::span{c_array};
    std::cout << sizeof(sca) << '\n';
}
```

Output:

```
16
8
8
```

I didn't test the ROM footprint because I don't have a C++20 compatible embedded compiler on my computer yet.

## Conclusion

OK, spans look amazing 😍

I believe they will help write me better code, mainly when I deal with plain arrays I get from C code. Spans will provide a good solution (safe, standard, portable) to carry the size of the data along with a pointer to them. They will probably be a nice bridge between the C and the C++ words.