C++20 span tutorial

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 << '

' ; 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:

" ; 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:

" ; 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:

" ; 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:

" ; 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:

" ; 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:

" ; 28 print_content ( s2 ); 29 std :: cout << "Original data, partially sorted:

" ; 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 << '

' ; 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:

" ; 28 print_content ( site_name ); 29 std :: cout << "Uppersized string:

" ; 30 uppersize ( site_name ); 31 print_content ( site_name ); 32 33 std :: cout << '

' ; 34 35 char site_subtitle []{ "My programming ramblings" }; 36 std :: cout << "Original char*:

" ; 37 print_content ( site_subtitle ); 38 std :: cout << "Uppersized char*:

" ; 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: