std::span std::span  / gsl::span

Replaces (Pointer, Length) Pairs Replaces (Pointer, Length) What for?

  • lightweight
  • non-owning view (= not responsible for allocating or deleting memory)
  • into contiguous memory block (of e.g., std::vector, C-array, …)
span<int> sequence of integers whose values can be changed
span<int const> sequence of integers whose values can't be changed
span<int,5> sequence of exactly 5 integers (number of values fixed at compile time)
void foo(span<int> s); void foo(int* a, int n);
  • well-expressed intent:
    foo accesses a sequence of ints and doesn't take ownership of memory
  • ambiguous semantics:
    does foo take ownership of the array (delete it afterwards)?
  • 1 parameter per data source ⇒ easy to call correctly
  • 2 parameters per data source ⇒ unnecessary confusion, especially when functions take multiple arrays
  • foo's implementation can use standard library conforming interface:
    empty, size, range-based for, begin, end, …
  • ugly and error-prone code inside foo that is specific to handling arrays: 2 parameters whose values can vary independently, nullptr-checks, …

Using Spans Using

#include <gsl/span>   + 
#include <span>      

As Parameter

void print_ints  (span<int const> s);
void print_chars (span<char const> s);
void modify_ints (span<int> s);
Call With Container/Range:
std::vector<int> v {1,2,3,4};
print_ints( v );  

std::array<int,3> a {1,2,3}; print_ints( a );
std::string s = "Some Text"; print_chars( s );
std::string_view sv = s; print_chars( sv );
Call With Iterator Range:
std::vector<int> v {1,2,3,4,5,6,7,8};

// iterator range: print_ints( {begin(v)+2, end(v)} ); print_ints( {begin(v)+2, begin(v)+5} );
// iterator + length: print_ints( {begin(v)+2, 3} );
Call With Stack-Array:
int a[100];  
print_ints( a );
Call With Poiner & Length:
auto n = get_number_of_elements();
auto p = get_pointer_to_memory();
print_ints( {p, n} );

A span decouples the storage strategy for sequential data from code that only needs to access the elements in the sequence, but not alter its structure.

Size & Data Access

std::span<int> s = ;
if (s.empty()) return;
if (s.size() < 1024) {  }
// spans in range-based for loops
for (auto x : s) {  }

// indexed access s[0] = 8; if (s[2] > 0) { }
// iterator access auto m1 = std::min(s.begin(), s.end()); auto m2 = std::min(begin(s), end(s)); auto m3 = std::ranges::min(s);

Comparing Spans

std::vector<int> v {1,2,3,4};
std::vector<int> w {1,2,3,4};
std::span s1 {v};
std::span s2 {w};
bool values_same = s1 == s2;  // true
bool memory_same   = s1.data() == s2.data();  // false

Spans From Spans

std::span<> s = ;
auto first3elements = s.first(3);
auto last3elements  = s.last(3);
size_t offset = 2;
size_t count = 4;
auto subs = s.subspan(offset, count);

std::span<std::byte const> b = s.as_bytes(); std::span<std::byte> wb = s.as_writable_bytes();

Making Spans Making Spans Making

In C++17 and C++20, span's template parameters can be deduced from the constructor arguments.

std::vector<int>  w {0, 1, 2, 3, 4, 5, 6};
std::array<int,4> a {0, 1, 2, 3};

std::span sw1 { w }; std::span sa1 { a }; // explicit read-only view: std::span sw2 { std::as_const(w) };
gsl::span<int> sw3 { w }; gsl::span<int,4> sa2 { a }; // explicit read-only view: gsl::span<int const> sw4 { a };
std::vector<int> w {0, 1, 2, 3, 4, 5, 6};
                          |----.------'
std::span      s1 {begin(w)+2, 5};  
std::span      s2 {begin(w)+2, end(w)}; 

gsl::span<int> s3 {begin(w)+2, 5};  
gsl::span<int> s4 {begin(w)+2, end(w)};
int a[100];  
// auto-deduces type + size
std::span s1 { a };  

// no parameter deduction gsl::span<int> s2 { a }; gsl::span<int,100> s2 { a }; // constexpr size
auto len = get_length_at_runtime();
auto p = std::make_unique<int[]>(len);  
std::span      s1 {p.get(), len};  
gsl::span<int> s2 {p.get(), len};  

Careful!

std::span s;
if (  ) {
  std::vector<int> w {1,2,3,4,5};
  s = std::span(w);
} // w destroyed!
cout << s[0]; //  UNDEFINED BEHAVIOR: w's memory destroyed!
std::vector<int> w {1,2,3,4,5};
std::span s {w};
w.insert(begin(w), {-1,-2,0});
cout << s[0]; //  UNDEFINED BEHAVIOR: w might hold new memory