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: