The Guidelines Support Library is a Microsoft implementation of some of the types and functions described in the C++ Core Guidelines maintained by the Standard C++ Foundation. Among the types provided by the GSL is span<T> formerly known as array_view<T>. This article is an introduction to this type.
span<T> is a non-owning range of contiguous memory recommended to be used instead of pointers (and size counter) or standard containers (such as std::vector or std::array).
Suppose you want to create a function that displays the content of a container. Such a function could look like this:
|
template <typename T> void display(std::vector<T> const & data) { for (auto const e : data) std::cout << e << ' '; std::cout << std::endl; } std::vector<int> v{ 1, 2, 3, 4, 5 }; display(v); |
This will work with vectors, but not with arrays or lists. So then you need overloads in order to support other containers.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
template <class Iterator> void display(Iterator const b, Iterator const e) { for (Iterator i = b; i != e; ++i) std::cout << *i << ' '; std::cout << std::endl; } template <typename T> void display(std::vector<T> const & data) { display(std::begin(data), std::end(data)); } template <typename T, size_t S> void display(std::array<T, S> const & data) { display(std::begin(data), std::end(data)); } std::vector<int> v{ 1, 2, 3, 4, 5 }; display(v); std::array<int, 5> a{1, 2, 3, 4, 5}; display(a); |
But what if you now what to display the content of an int[] or an int*?
The span<T> type is intended as a uniform interface over arrays, pointers and standard containers that can be used as a replacement of these types. It does not store a copy of the original data, only a pointer to data and counters.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
template <typename T> void display(gsl::span<T> const & data) { for(auto const e : data) std::cout << e << ' '; std::cout << std::endl; } std::vector<int> v{ 1, 2, 3, 4, 5 }; gsl::span<int> s1{ v }; display(s1); std::array<int, 5> a{1, 2, 3, 4, 5}; gsl::span<int> s2{ a }; display(s2); int arr[] = { 1, 2, 3, 4, 5 }; gsl::span<int> s3{ arr }; display(s3); |
The following helper functions are used in the samples below:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
template <class Iterator> void show(Iterator b, Iterator e) { for (auto i = b; i != e; ++i) { std::cout << *i << ' '; } std::cout << std::endl; } template <class T> void show(gsl::span<T> const & s) { show(std::begin(s), std::end(s)); } template <class T, size_t R> void show(gsl::span<T, R> const & s) { show(std::begin(s), std::end(s)); } template <class T, size_t R, size_t C> void show(gsl::span<T, R, C> const & s) { show(std::begin(s), std::end(s)); } template <class T, size_t R, size_t C> void show(gsl::span<T, gsl::dynamic_range, R, C> const & s) { show(std::begin(s), std::end(s)); } |
Creating a span
A span can be created in many ways, including:
- from a single value (variable, not a literal)
|
int n = 1; gsl::span<int> s1 = n; // s1 = {1} gsl::span<int, 1> s2 = n; // se = {1} |
- from a pointer and number of elements
|
std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; gsl::span<int> s1(v.data(), 4); // s1 = {1, 2, 3, 4} gsl::span<int, 4> s2(v.data(), 4); // s2 = {1, 2, 3, 4} |
- from a begin and end pointer
|
std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; gsl::span<int> s1(&v[0], &v[4]); // s1 = {1, 2, 3, 4} gsl::span<int, 4> s2(&v[0], &v[4]); // s2 = {1, 2, 3, 4} |
- from a C-like array
|
int arr[] = { 1, 2, 3, 4, 5 }; gsl::span<int> s1{ arr }; // s1 = {1, 2, 3, 4, 5} gsl::span<int, 5> s2{ arr }; // s2 = {1, 2, 3, 4, 5} int arr2[2][3] = {1, 2, 3, 4, 5, 6}; gsl::span<int> s3{ arr2 }; // s3 = {1, 2, 3, 4, 5, 6} gsl::span<int, 6> s4{ arr2 }; // s3 = {1, 2, 3, 4, 5, 6} gsl::span<int, 2, 3> s5{ arr2 }; // s3 = {1, 2, 3, 4, 5, 6} |
- from a dynamic array
|
int (*arr)[3][3] = new int[2][3][3]; for (int i = 0; i < 2; ++i) { for (int j = 0; j < 3; j++) { for (int k = 0; k < 3; ++k) { arr[i][j][k] = k + j * 3 + i * 9; } } } gsl::span<int, gsl::dynamic_range, 3, 4> s1{ arr, 2 }; show(s1); // prints 0 1 2 3 4 .. 17 delete[] arr; |
- from a standard container with contiguous memory layout such as array, vector or string
|
std::array<int, 5> a = { 1, 2, 3, 4, 5 }; gsl::span<int> s1{ a }; // s1 = {1, 2, 3, 4, 5, 6} gsl::span<int, 5> s2{ a }; // s2 = {1, 2, 3, 4, 5, 6} std::vector<int> v = { 1, 2, 3, 4, 5 }; gsl::span<int> s3{ v }; // s3 = {1, 2, 3, 4, 5, 6} gsl::span<int, 5> s4{ v }; // s4 = {1, 2, 3, 4, 5, 6} std::string text = "sample"; gsl::span<const char> s5{ text }; // s5 = {'s', 'a', 'm', 'p', 'l', 'e'} |
- using the gsl::as_span() function:
|
std::vector<int> v = { 1, 2, 3, 4, 5 }; auto sv = gsl::as_span(v); // sv = {1, 2, 3, 4, 5} std::array<int, 5> a = { 1, 2, 3, 4, 5 }; auto sa = gsl::as_span(a); // sa = {1, 2, 3, 4, 5} |
Notice that it is not possible to create a span from an initializer_list because an initializer list is a temporary object and a span is a non-owning container, it does not make a copy of the data, and therefore it can end up containing dangling references to temporary data. For a detailed discussion on the topic see this issue.
Size of a span
A span can have zero, one or more dimensions, and each dimension can have a different size (number of elements). The number of dimensions is called rank and the number of elements in a dimension is called extent. You can retrieve the rank and extent using the functions with the same name.
|
gsl::span<int> s0{ nullptr }; // rank = 1, extent[0] = 0 gsl::span<int> s1; // rank = 1, extent[0] = 0 int arr[] = { 1, 2, 3, 4, 5 }; gsl::span<int> s2{ arr }; // rank = 1, extent[0] = 5 gsl::span<int, 5> s3{ arr }; // rank = 1, extent[0] = 5 std::cout << "rank=" << s2.rank() << std::endl; // rank=1 std::cout << "extent(0)=" << s2.extent(0) << std::endl; // extent[0]=5 std::cout << "extent<0>=" << s2.extent<0>() << std::endl; // extent[0]=5 gsl::span<int, 2, 3> s4{ arr, 6 }; // rank = 2, extent[0] = 2, extent[1] = 3 std::cout << "rank=" << s4.rank() << std::endl; // rank=2 std::cout << "extent(0)=" << s4.extent(0) << std::endl; // extent[0]=2 std::cout << "extent(1)=" << s4.extent(1) << std::endl; // extent[1]=3 |
Subspans
It is possible to create subspans from a span. There are several functions that do that:
- first(): returns the sub-span with the first N elements from the original span
- last(): returns the sub-span with the last N elements from the original span
- subspan(): returns the sub-span within the specified range (first and last positions) of the original span.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; gsl::span<int> s{ arr }; auto fs1 = s.first(0); // fs1 = {} auto fs2 = s.first(5); // fs2 = {1, 2, 3, 4, 5} auto fs3 = s.first<3>(); // fs3 = {1, 2, 3} auto ls1 = s.last(0); // ls1 = {} auto ls2 = s.last(5); // ls2 = {6, 7, 8, 9, 10} auto ls3 = s.last<3>(); // ls3 = {8, 9, 10} auto ns1 = s.subspan(0, 0); // ns1 = {} auto ns2 = s.subspan(0, 5); // ns2 = {1, 2, 3, 4, 5} auto ns3 = s.subspan(5, 0); // ns3 = {} auto ns4 = s.subspan<0, 0>(); // ns4 = {} auto ns5 = s.subspan<0, 5>(); // ns5 = {1, 2, 3, 4, 5} auto ns6 = s.subspan<5, 0>(); // ns6 = {} |
Comparisons
You can use the comparison operators (==, !=, <, <=, >, >=) with two spans. Equality is checked with std::equal (two ranges are equal if every element in the first range is equal to the element corresponding to the same position in the second range) and less/greater is checked with std::lexicographical_compare() (one range is less than another if the first mismatch element in the first range is less than the element on the same position in the second range).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
int arr1[2][3] = { 1, 2, 3, 4, 5, 6 }; gsl::span<int, 6> s1{ arr1 }; int arr2[] = { 1, 2, 3, 4, 5, 6 }; gsl::span<int> s2{ arr2 }; int arr3[] = { 1, 2, 3 }; gsl::span<int> s3{ arr3 }; int arr4[] = { 3, 2, 1 }; gsl::span<int> s4{ arr4 }; std::cout << (s1 == s2) << std::endl; // prints 1 std::cout << (s2 == s3) << std::endl; // prints 0 std::cout << (s2 > s3) << std::endl; // prints 1 std::cout << (s4 != s3) << std::endl; // prints 1 std::cout << (s4 > s3) << std::endl; // prints 1 |
Element access
It is possible to access the content of a span either with iterators or indexes.
|
std::vector<int> v = { 1, 2, 3, 4, 5, 6 }; gsl::span<int> s{ v }; // prints 1 2 3 4 5 6 for (auto const & e : s) std::cout << e << std::endl; // prints 6 5 4 3 2 1 for (auto it = s.rbegin(); it != s.rend(); ++it) std::cout << *it << std::endl; // prints 1 2 3 4 5 6 for (auto it = std::begin(s); it != std::end(s); ++it) std::cout << *it << std::endl; |
When it comes to index access you can either index like a regular array (s[0], s[1][2], etc.) or using a special type called index.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; gsl::span<int> s1{ v }; gsl::span<int, 3, 4> s2{ v }; // prints 1 2 3 ... 12 for (int i = 0; i < s1.extent(0); ++i) std::cout << s1[i] << ' '; std::cout << std::endl; // prints // 1 2 3 4 // 5 6 7 8 // 9 10 11 12 for (int i = 0; i < s2.extent(0); ++i) { for (int j = 0; j < s2.extent(1); ++j) std::cout << s2[i][j] << ' '; std::cout << std::endl; } // uni-dimensional index gsl::index<1> i1 = { 0 }; std::cout << s1[i1] << std::endl; // prints 1 // bi-dimensional index gsl::index<2> i2 = { 1, 1 }; std::cout << s2[i2] << std::endl; // prints 6 // bi-dimensional index computed from another index gsl::index<2> i3 = i2 * 2; std::cout << s2[i3] << std::endl; // prints 11 |
Like this:
Like Loading...