4

C++20 span tutorial

 3 years ago
source link: https://solarianprogrammer.com/2019/11/03/cpp-20-span-tutorial/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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:

1798834065.jpg

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

396730017X.jpg

Show Comments


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK