std::span in C++20: More Details

std::span in C++20: More Details

This post is a cross-post from www.ModernesCpp.com.

A std::span represents an object that refers to a contiguous sequence of objects. Today, I want to write about its not-so-obvious features.

A std::span, sometimes also called a view, is never an owner. This contiguous memory can be a plain array, a pointer with a size, a std::array, a std::vector, or a std::string. A typical implementation consists of a pointer to its first element and a size. The main reason for having a std::span<T> is that a plain array will decay to a pointer if passed to a function; therefore, the size is lost. This decay is a typical reason for errors in C/C++.

Automatically deduces the size of a contiguous sequence of objects

In contrast, std::span<T> automatically deduces the size of contiguous sequences of objects.

// printSpan.cpp

#include <iostream>
#include <vector>
#include <array>
#include <span>

void printMe(std::span<int> container) {
    
    std::cout << "container.size(): " << container.size() << '\n';  // (4)
    for(auto e : container) std::cout << e << ' ';
    std::cout << "\n\n";
}

int main() {
    
    std::cout << std::endl;
    
    int arr[]{1, 2, 3, 4};              // (1)
    printMe(arr);
    
    std::vector vec{1, 2, 3, 4, 5};     // (2)
    printMe(vec);

    std::array arr2{1, 2, 3, 4, 5, 6}; // (3)
    printMe(arr2);
    
}
        

The C-array (1), std::vector?(2), and the std::array (3) have int‘s. Consequently, std::span also holds int’s. There is something more interesting in this simple example. For each container, std::span can deduce its size (4).

This was a short reminder about std::span. For the full story, read my previous post “std::span in C++20: Bounds-Safe Views of Sequences of Objects“.

A std::span can have a static extent or a dynamic extent.

Static versus Dynamic Extent

By default, std::span has a dynamic extent:

template <typename T, std::size_t Extent = std::dynamic_extent>
class span;
        

When a std::span has a static extent, its size is known at compile time and part of the type: std::span. Consequently, its implementation needs only a pointer to the first element of the contiguous sequence of objects.

Implementing a std::span with a dynamic extent consists of a pointer to the first element and the size of the contiguous sequence of objects. The size is not part of the std::span type.

The next example emphasizes the differences between the two kinds of spans.

// staticDynamicExtentSpan.cpp

#include <iostream>
#include <span>
#include <vector>

void printMe(std::span<int> container) {        // (3)  
    
    std::cout << "container.size(): " << container.size() << '\n';
    for (auto e : container) std::cout << e << ' ';
    std::cout << "\n\n";
}

int main() {

    std::cout << '\n';

    std::vector myVec1{1, 2, 3, 4, 5};        
    std::vector myVec2{6, 7, 8, 9};

    std::span<int> dynamicSpan(myVec1);          // (1)
    std::span<int, 4> staticSpan(myVec2);        // (2)

    printMe(dynamicSpan);
    printMe(staticSpan);

    // staticSpan = dynamicSpan;    ERROR        // (4)
    dynamicSpan = staticSpan;                    // (5) 

    printMe(staticSpan);                         // (6)

    std::cout << '\n';
    
}
        

dynamicSpan (line 1) has a dynamic extent, while staticSpan (line 2) has a static extent. Both std::spans return their size in the printMe function (line 3). A std::span with static extent can be assigned to a std::span with dynamic extent, but not vice versa. Line 4 would cause an error, but lines 5, and 6 are valid.

There is one particular use case of std::span. A std::span can be a constant range of modifiable elements.


Modernes C++ Mentoring

Be part of my mentoring programs:

Do you want to stay informed about my mentoring programs: Subscribe via E-Mail.

?

A Constant Range of Modifiable Elements

For simplicity, I name a std::vector and a std::span range. A std::vector models a modifiable range of modifiable elements: std::vector. When you declare this std::vector as const, it models a constant range of constant objects: const std::vector. You cannot model a constant range of modifiable elements. This is where std::span comes into play. A std::span models a constant range of modifiable objects: std::span. The following image emphasizes the variations of (constant/modifiable) ranges and (constant/modifiable) elements.

// constRangeModifiableElements.cpp

#include <iostream>
#include <span>
#include <vector>

void printMe(std::span<int> container) {
    
    std::cout << "container.size(): " << container.size() << '\n';  
    for (auto e : container) std::cout << e << ' ';
    std::cout << "\n\n";
}

int main() {

    std::cout << '\n';

    std::vector<int> origVec{1, 2, 2, 4, 5};

    // Modifiable range of modifiable elements
    std::vector<int> dynamVec = origVec;           // (1)
    dynamVec[2] = 3;
    dynamVec.push_back(6);
    printMe(dynamVec);

    // Constant range of constant elements
    const std::vector<int> constVec = origVec;     // (2)
    // constVec[2] = 3;        ERROR
    // constVec.push_back(6);  ERROR
    std::span<const int> constSpan(origVec);       // (3)
    // constSpan[2] = 3;       ERROR

    // Constant range of modifiable elements
    std::span<int> dynamSpan{origVec};             // (4)
    dynamSpan[2] = 3;
    printMe(dynamSpan);

    std::cout << '\n';

}
        

The vector dynamVec (line 1) is a modifiable range of modifiable elements. This observation does not hold for the vector constVec (line 2). Neither can constVec change its elements nor its size. constSpan (line 3) behaves accordingly. dynamSpan (line 4) models the unique use case of a constant range of modifiable elements.

Finally, I want to mention two dangers you should know when using std::span.

Dangers of std::span

The typical issues of std::span are twofold. First, a std::span should not act on a temporary and second, the size of the underlying contiguous range of a std::span should not be modified.

A std::span on a Temporary

A std::span is never an owner. Therefore, a std::span does not extend the lifetime of its underlying data. Consequently, a std::span should only operate on an lvalue. Using std::span on a temporary range is undefined behavior.

// temporarySpan.cpp

#include <iostream>
#include <span>
#include <vector>

std::vector<int> getVector() {                          // (2)
    return {1, 2, 3, 4, 5};
}

int main() {

     std::cout << '\n';
    
    std::vector<int> myVec{1, 2, 3, 4, 5};              // (1)
    std::span<int, 5> mySpan1{myVec};                  
    std::span<int, 5> mySpan2{getVector().begin(), 5};  // (3)

    for (auto v: std::span{myVec}) std::cout << v << " ";
    std::cout << '\n';
    for (auto v: std::span{getVector().begin(), 5}) std::cout << v << " ";  // (4)

     std::cout << "\n\n";
    
}
        

Using a std::span with a static extent or a std::span with a dynamic extent on the lvalue is fine. When I switch from the lvalue std::vector in line 1 to a temporary std::vector, given by the function getVector (lines 2), the program has undefined behavior. Both lines 3 and 4 are not valid. Consequently, executing the program exposes the undefined behavior. The output of line 4 does not match with the std::vector, generated by the function getVector().

Changing the Size of the Underlying Contiguous Range

When you change the size of the underlying contiguous range, the contiguous range may be reallocated, and the std::span refers to stale data.

std::vector<int> myVec{1, 2, 3, 4, 5};

std::span<int> sp1{myVec};

myVec.push_back(6);  // undefined behavior
        

What’s Next?

In my next post, I dive once more in the formatting library of C++20.

Post Views: 1

Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dr?ge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschl?ger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, moon, and Philipp Lenk.

Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

My special thanks to Embarcadero, PVS-Studio, Tipi.build, Take Up Code, and SHAVEDYAKS.

Seminars

I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.

Standard Seminars (English/German)

Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.

  • C++ – The Core Language
  • C++ – The Standard Library
  • C++ – Compact
  • C++11 and C++14
  • Concurrency with Modern C++
  • Design Pattern and Architectural Pattern with C++
  • Embedded Programming with Modern C++
  • Generic Programming (Templates) with C++

New

  • Clean Code with Modern C++
  • C++20

Contact Me


Modernes C++ Mentoring


要查看或添加评论,请登录

Rainer Grimm的更多文章

  • std::execution: Asynchronous Algorithms

    std::execution: Asynchronous Algorithms

    supports many asynchronous algorithms for various workflows. Presenting proposal P2300R10 is not easy.

  • My ALS Journey (17/n): Christmas Special

    My ALS Journey (17/n): Christmas Special

    Today, I have a special Christmas gift. My ALS Journey so far Make the Difference Let’s do something great together:…

  • std::execution

    std::execution

    std::execution, previously known as executors or Senders/Receivers, provides “a Standard C++ framework for managing…

    1 条评论
  • C++26 Core Language: Small Improvements

    C++26 Core Language: Small Improvements

    There are more small improvements in the C++26 language, which you should know. static_assert extension First, here’s…

  • My ALS Journey (16/n): Good Bye Training / Hello Mentoring

    My ALS Journey (16/n): Good Bye Training / Hello Mentoring

    In 2025, I will no longer offer C++ classes. Instead, I will only offer C++ mentoring in the future.

    1 条评论
  • Placeholders and Extended Character Set

    Placeholders and Extended Character Set

    Placeholders are a nice way to highlight variables that are no longer needed. Additionally, the character set of C++26…

    4 条评论
  • Contracts in C++26

    Contracts in C++26

    Contracts allow you to specify preconditions, postconditions, and invariants for functions. Contracts should already be…

    5 条评论
  • Mentoring as a Key to Success

    Mentoring as a Key to Success

    I know that we are going through very challenging times. Saving is the top priority.

  • Reflection in C++26: Determine the Layout

    Reflection in C++26: Determine the Layout

    Thanks to reflection, you can determine the layout of types. My examples are based on the reflection proposal P2996R5.

    5 条评论
  • My ALS Journey (15/n): A typical Day

    My ALS Journey (15/n): A typical Day

    You may wonder how my day looks. Let me compare a day before ALS with a current day.

    3 条评论

社区洞察

其他会员也浏览了