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