Solarian Programmer

My programming ramblings

C++20 span tutorial

Posted on November 3, 2019 by Paul

According to the latest C++20 draft, a span is a non-owning view over a contiguous sequence of objects. In other words, a std::span is, in essence, a pointer, length pair that gives the user a view into a contiguous sequence of elements. The elements of a span can be, for example, stored in one of the standard library sequential containers (like std::array, std::vector), in a built-in C-style array or in a memory buffer.

Here is a simple example of using std::span as a general interface for a print function that receives as argument a contiguous sequence of integers:

 1 // span_0.cpp
 2 #include <iostream>
 3 #include <vector>
 4 #include <array>
 5 #include <span>
 6 
 7 void print_content(std::span<int> container) {
 8     for(const auto &e : container) {
 9         std::cout << e << ' ';
10     }
11     std::cout << '\n';
12 }
13 
14 int main() {
15     int a[]{23, 45, 67, 89};
16     print_content(a);
17 
18     std::vector v{1, 2, 3, 4, 5};
19     print_content(v);
20 
21     std::array a2{-14, 55, 24, 67};
22     print_content(a2);
23 }

This is what I see if I build and run the above file on a Linux machine with Clang 9:

1 $ clang++ -std=c++2a -stdlib=libc++ -Wall -Wextra -pedantic span_0.cpp
2 $ ./a.out
3 23 45 67 89
4 1 2 3 4 5
5 -14 55 24 67
6 $

At the time of this writing, you can use std::span with Clang 9 and GCC 10, MSVC doesn’t have support for std::span. If you are using a compiler that doesn’t have support for std::span, you can use a 3rd party implementation like @tcbrindle or use the latest Clang or GCC from Compiler Explorer.

Here is an example of modifying the above program for a C++17 compiler. I assume that you’ve saved the span.hpp header from @tcbrindle in the same folder as your span_0.cpp file:

 1 // span_0.cpp
 2 #include <iostream>
 3 #include <vector>
 4 #include <array>
 5 
 6 #if __has_include(<span>)
 7     #include <span>
 8 #else
 9     #include "span.hpp"
10     // UGLY TEMPORARY HACK UNTIL YOUR CURRENT C++ COMPILER INCLUDES std::span SUPPORT
11     namespace std {
12         using tcb::span;
13     }
14 #endif
15 
16 
17 // ... same code as in the previous example

With the above modification, this is how you can build the example with the latest MSVC compiler:

1 C:\DEV> cl /std:c++latest /W4 /permissive- /EHsc span_0.cpp
2 C:\DEV>span_0.exe
3 23 45 67 89
4 1 2 3 4 5
5 -14 55 24 67
6 
7 C:\DEV>

Please note that while std::span doesn’t own the memory storage of the elements, as in you can’t increase or decrease the memory buffer that stores the elements, you can modify the actual elements, e.g.:

 1 // span_1.cpp
 2 
 3 // ... include headers ...
 4 
 5 void print_content(std::span<int> container) {
 6     // ...
 7 }
 8 
 9 void scale_2x_content(std::span<int> container) {
10     for(auto &e : container) {
11         e *= 2;
12     }
13 }
14 
15 int main() {
16     int a[]{23, 45, 67, 89};
17     scale_2x_content(a);
18     print_content(a);
19 
20     std::vector v{1, 2, 3, 4, 5};
21     scale_2x_content(v);
22     print_content(v);
23 
24     std::array a2{-14, 55, 24, 67};
25     scale_2x_content(a2);
26     print_content(a2);
27 }

This is what I see, if I build and run the above program:

1 $ clang++ -std=c++2a -stdlib=libc++ -Wall -Wextra -pedantic span_1.cpp
2 $ ./a.out
3 46 90 134 178
4 2 4 6 8 10
5 -28 110 48 134
6 $

You can also create a span from a pointer to a memory buffer and a size, e.g. :

 1 // span_2.cpp
 2 #include <iostream>
 3 #include <vector>
 4 #include <array>
 5 #include <span>
 6 #include <memory>
 7 
 8 void print_content(std::span<int> container) {
 9     // ...
10 }
11 
12 void scale_2x_content(std::span<int> container) {
13     // ...
14 }
15 
16 int main() {
17 
18     // ...
19 
20     // Allocate space for 10 integers on the heap
21     size_t sz{10};
22     auto p = std::make_unique<int[]>(sz);
23     // Fill the previously allocated space
24     for(size_t i = 0; i < sz; ++i) {
25         p[i] = static_cast<int>(i);
26     }
27     // Pass a pointer/size pair to functions allow a std::span as the first argument
28     scale_2x_content({p.get(), sz});
29     print_content({p.get(), sz});
30 }

This is what I see, if I build and run the above program:

1 $ clang++ -std=c++2a -stdlib=libc++ -Wall -Wextra -pedantic span_2.cpp
2 $ ./a.out
3 46 90 134 178
4 2 4 6 8 10
5 -28 110 48 134
6 0 2 4 6 8 10 12 14 16 18
7 $

You can also create subviews, or subspans, from a span and operate on these. Keep in mind that when you modify a span or a subspan element you are actually modifying the original data, e.g.:

 1 // span_3.cpp
 2 #include <iostream>
 3 #include <span>
 4 #include <iterator>
 5 
 6 void print_content(std::span<int> container) {
 7     // ...
 8 }
 9 
10 void scale_2x_content(std::span<int> container) {
11     // ...
12 }
13 
14 int main() {
15     int a[]{44, -15, 45, 67, 89, 66};
16     std::cout << "Original data:\n";
17     print_content(a);
18 
19     // Create a span from a C-style array
20     std::span s1{a, std::size(a)};
21 
22     // Double the subview/subspan elements created from the first 4 elements
23     // of the above s1 span
24     scale_2x_content(s1.first(4));
25     std::cout << "Double the first 4 elements:\n";
26     print_content(a);
27 
28     // Double the subview/subspan elements created from the last 3 elements
29     // of the above s1 span
30     scale_2x_content(s1.last(3));
31     std::cout << "Double the last 3 elements:\n";
32     print_content(a);
33 }

This is what you should see, if you build and run the above code:

1 $ clang++ -std=c++2a -stdlib=libc++ -Wall -Wextra -pedantic span_3.cpp
2 $ ./a.out
3 Original data:
4 44 -15 45 67 89 66
5 Double the first 4 elements:
6 88 -30 90 134 89 66
7 Double the last 3 elements:
8 88 -30 90 268 178 132
9 $

Say that you want to sort a subspan from an existing contiguous sequence of integers. Here is an example of creating a subspan from an existing C-array, taking a subspan from the above span, without the first and the last elements and sorting the selected subspan:

 1 // span_4.cpp
 2 #include <iostream>
 3 #include <span>
 4 #include <iterator>
 5 #include <algorithm>
 6 
 7 void print_content(std::span<int> container) {
 8     // ...
 9 }
10 
11 int main() {
12     int a[]{44, 45, -15, 89, 6, 66};
13     std::cout << "Original data:\n";
14     print_content(a);
15 
16     // Create a span from a C-style array
17     std::span s1{a, std::size(a)};
18 
19     // Create and print a subspan from the above s1 span
20     // without the first and last elements:
21     std::span s2{s1.subspan(1, s1.size() - 2)};
22     std::cout << "Subspan/subview from the original data without the first and last elements:\n";
23     print_content(s2);
24 
25     // Sort the s2 subspan and print the sorted subspan and the original data:
26     std::sort(s2.begin(), s2.end());
27     std::cout << "Sorted subspan:\n";
28     print_content(s2);
29     std::cout << "Original data, partially sorted:\n";
30     print_content(a);
31 }

This is what you should see, if you build and run the above code:

 1 $ clang++ -std=c++2a -stdlib=libc++ -Wall -Wextra -pedantic span_4.cpp
 2 $ ./a.out
 3 Original data:
 4 44 45 -15 89 6 66
 5 Subspan/subview from the original data without the first and last elements:
 6 45 -15 89 6
 7 Sorted subspan:
 8 -15 6 45 89
 9 Original data, partially sorted:
10 44 -15 6 45 89 66
11 $

As a side note, don’t confuse C++17 std::string_view with the std::span introduced by C++20. While both are non-owning views, std::string_view is a read-only view.

You can, obviously, create a modifiable span or subspan from a std::string or from a char pointer and a buffer size pair. Here is a simple example, that works only for ASCII strings:

 1 // span_5.cpp
 2 #include <iostream>
 3 #include <span>
 4 #include <string>
 5 #include <cctype>
 6 #include <stdexcept>
 7 
 8 void print_content(std::span<char> container) {
 9     for(const auto &e : container) {
10         std::cout << e;
11     }
12     std::cout << '\n';
13 }
14 
15 void uppersize(std::span<char> container) {
16     for(auto &e : container) {
17         unsigned char tmp = static_cast<unsigned char>(e);
18         if(tmp > 127) {
19             throw std::runtime_error("Error! Undefined conversion for non ASCII input strings!");
20         }
21         e = std::toupper(tmp);
22     }
23 }
24 
25 int main() {
26     std::string site_name{"Solarian Programmer"};
27     std::cout << "Original string:\n";
28     print_content(site_name);
29     std::cout << "Uppersized string:\n";
30     uppersize(site_name);
31     print_content(site_name);
32 
33     std::cout << '\n';
34 
35     char site_subtitle[]{"My programming ramblings"};
36     std::cout << "Original char*:\n";
37     print_content(site_subtitle);
38     std::cout << "Uppersized char*:\n";
39     uppersize(site_subtitle);
40     print_content(site_subtitle);
41 }

This is what you should see, if you build and run the above code:

 1 $ clang++ -std=c++2a -stdlib=libc++ -Wall -Wextra -pedantic span_5.cpp
 2 $ ./a.out
 3 Original string:
 4 Solarian Programmer
 5 Uppersized string:
 6 SOLARIAN PROGRAMMER
 7 
 8 Original char*:
 9 My programming ramblings
10 Uppersized char*:
11 MY PROGRAMMING RAMBLINGS
12 $

You can find the complete source code on the GitHub repository for this article.

If you want to learn more about C++17 I would recommend reading C++17 in Detail by Bartlomiej Filipek:

or, C++17 - The Complete Guide by Nicolai M. Josuttis:


Show Comments