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:
1 2 3 4 5 6 7 8 9 |
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)
123int n = 1;gsl::span<int> s1 = n; // s1 = {1}gsl::span<int, 1> s2 = n; // se = {1}
- from a pointer and number of elements
123std::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
123std::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
12345678int 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
12345678910111213141516int (*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 .. 17delete[] arr;
- from a standard container with contiguous memory layout such as array, vector or string
12345678910std::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:
12345std::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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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.
1 2 3 4 5 6 7 8 9 |
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 |