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 exampleWith 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:

