Beginner's Guide
    First Steps
    Input & Output
    Custom Types – Part 1
    Diagnostics
    Standard Library – Part 1
    Function Objects
    Standard Library – Part 2
    Code Organization
    Custom Types – Part 2
    Generic Programming
    Memory Management
    Software Design Basics

    Sequence Views Sequence Views Views

    An object is said to be an owner of a resource (memory, file handle, connection, thread, lock, …) if it is responsible for its lifetime (initialization/creation, finalization/destruction).

    std::string_view string_view C++17

    #include <string_view>
    • lightweight  (= cheap to copy, can be passed by value)
    • non-owning  (= not responsible for allocating or deleting memory)
    • read-only view  (= does not allow modification of target string)
    • of a character range or string(-like) object  (std::string / "literal" / …)
    • primary use case: read-only function parameters  (avoids temporary copies)

    Avoids Unnecessary Memory Allocations

    We don't want/expect additional copies or memory allocations for a read-only parameter!

    The traditional choice std::string const& is problematic:

    • A std::string can be constructed from string literals or an iterator range to a char sequence.
    • If we pass an object as function argument that is not a string itself, but something that can be used to construct a string, e.g., a string literal or an iterator range, a new temporary string object will be allocated and bound to the const reference.
    #include <vector>
    #include <string>
    #include <string_view>
    
    void f_cref (std::string const& s) { … }
    void f_view (std::string_view s) { … }
    
    int main () {
      std::string stdStr = "Standard String";
      auto const cStr = "C-String";
      std::vector<char> v {'c','h','a','r','s','\0'};
      f_cref(stdStr);     // no copy
      f_cref(cStr);       //  temp copy
      f_cref("Literal");  //  temp copy
      f_cref({begin(v),end(v)});  //  temp copy
      f_view(stdStr);     // no copy
      f_view(cStr);       //  no copy
      f_view("Literal");  //  no copy
      f_view({begin(v),end(v)});  //  no copy
    }

    String-Like Function Parameters

    If You…

    Use Parameter Type

    always need a copy of the input string inside the function std::string
    pass by value
    want read-only access
    • don't (always) need a copy
    • are using C++17 / 20
    #include <string_view> std::string_view
    want read-only access
    • don't (always) need a copy
    • are stuck with C++98 / 11 / 14
    std::string const&
    pass by const reference
    want the function to modify the input string in-place (you should try to avoid such output parameters) std::string &
    pass by (non-const) reference

    See here for more explanations.

    Making string_views

    std::string s = "Some Text";
    // view whole string
    std::string_view sv1 { s };
    // view subrange
    std::string_view sv2 {begin(s)+2, begin(s)+5};
    std::string_view sv3 {begin(s)+2, end(s)}; 
    
    using namespace std::string_view_literals;
    auto literal_view = "C-String Literal"sv;
    cout << literal_view;
    
    std::string_view sv1 {std::string{"Text"}};
    cout << sv1; //  string object already destroyed!
    using namespace std::string_literals;
    std::string_view sv2 {"std::string Literal"s};
    cout << sv2; //  string object already destroyed!
    You should use string_view mainly as function parameter!

    string_view Interface

    std::span span C++20

    #include <span>
    • lightweight  (= cheap to copy, can be passed by value)
    • non-owning view  (= not responsible for allocating or deleting memory)
    • of a contiguous memory block  (of e.g., std::vectorstd::array, …)
    • primary use case: as function parameter  (container-independent access to values)
    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)
    span as a view into vector

    As Parameter (Primary Use Case) Parameters

    void print_ints  (std::span<int const> s);
    void print_chars (std::span<char const> s);
    void modify_ints (std::span<int> s);
    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 );  
    
    std::vector<int> v {1,2,3,4,5,6,7,8};
    // iterator range:
    print_ints( {begin(v), end(v)} );  
    print_ints( {begin(v)+2, end(v)} );  
    print_ints( {begin(v)+2, begin(v)+5} );  
    // iterator + length:
    print_ints( {begin(v)+2, 3} );  
    

    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.

    Explicitly Making Spans

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

    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_element(s.begin()s.end()); auto m2 = std::min_element(begin(s)end(s));

    Comparing Spans

    #include <algorithm>  // std::ranges::equal
    std::vector<int> v {1,2,3,4};
    std::vector<int> w {1,2,3,4};
    std::span sv {v};
    std::span sw {w};
    bool memory_same   = sv.data() == sw.data();  // false
    bool values_same = std::ranges::equal(sv,sw);  // true
    

    Making Spans From Spans

    std::vector<int> v {0,1,2,3,4,5,6,7,8};
    std::span s = v;
    auto first3elements = s.first(3);
    auto last3elements  = s.last(3);
    size_t offset = 2;
    size_t count = 4;
    auto subs = s.subspan(offset, count);
    

    Usage Guidelines

    Views As Function Parameters

    • decouple function implementations from the data representation / container type
    • clearly communicate the intent of only reading/altering elements in a sequence, but not modifying the underlying memory/data structure
    • make it easy to apply functions to sequence subranges
    • can almost never be dangling, i.e., refer to memory that has already been destroyed (because parameters outlive all function-local variables)
    • int foo (std::span<int const> in) { … }
      
      std::vector<int> v {…}; // v will always outlive parameter 'in'! foo(v); foo({begin(v), 5});
    • a view's target cannot invalidate the memory that the view refers to during the function's execution (unless it is done in another thread)
    • views can speed up accesses by avoiding a level of indirection:

      a const reference to a vector of ints potentially involves two indirections while a span into the vector can point directly at the vector's storage

    Careful When Returning Views

    • not always clear what object/memory the view refers to
    • returned views can be (inadvertently) invalidated
    // which parameter is the span's target?
    std::span<int const>
    foo (std::vector<int> const& x, std::vector<int> const& y);
    // we can assume that the returned span
    // refers to elements of the vector
    std::span<int const> random_subrange (std::vector<int> const& v);
    // however, this is still problematic:
    auto s = random_subrange(std::vector<int>{1,2,3,4});
    //  's' is dangling - vector object already destroyed!
    class Payments { 
    public:
      std::span<Money const> of (Customer const&) const;
      
    };
    Customer const& john = ;
    Payments pms = read_payments(file1);
    auto m = pms.of(john);
    pms = read_payments(file2);
    // depending on the implementation of Payments
    // m's target memory might no longer be valid
    // after the assignment

    Avoid Local View Variables

    • easy to produce dangling views, because we have to manually track lifetimes to ensure that no view outlives its target
    • even if the memory owner is still alive, it might invalidate the memory that a view is referring to
    std::string str1 = "Text";
    std::string_view sv {str1};
    if (  ) {
      std::string str2 = "Text";
      sv = str2;
    }
    cout << sv; //  str2 already destroyed!
    std::string_view sv1 {"C-String Literal"};
    cout << sv1; // 
    std::string_view sv2 {std::string{"Text"}};
    cout << sv2; //  string object already destroyed!
    using namespace std::string_literals;
    std::string_view sv3 {"std::string Literal"s};
    cout << sv3; //  string object already destroyed!

    Containers like vector might allocate new memory thus invalidating all views of it:

    std::vector<int> w {1,2,3,4,5};
    std::span s {w};
    w.insert(w.end(), {6,7,8,9});
    cout << s[0]; //  w might hold new memory

    Cheat Sheets

    std::string_view C++17

    (click for fullscreen view)

    std::span C++20

    (click for fullscreen view)