Last updated:2026-10-12
C++20 std::span Design and Pitfalls¶
0. Motivation¶
In traditional C/C++ code, we usually access an array with a pointer plus a size, for example:
span simplifies this operation, turning it into:
So what exactly is a span, what are its use cases, and what pitfalls does it have? This section explores these questions in detail.
1. What Is a span?¶
In C++20, a span is a lightweight, range-like container that provides access to a stretch of contiguous memory. As a view, a span does not own its data; it is simply a reference to the data, and it is commonly used to replace traditional C-style arrays or pointers. It can wrap arrays of any type as well as standard containers such as std::vector, providing a safer, more modern way to handle such data.
Several key points:
- Contiguous memory
- Does not own the data
- A reference
The source code of span in gcc lives at libstdc++-v3/include/std/span.
The span type takes two important parameters:
_Type: the element type.Extent: the array size. If it isdynamic_extent(the default), the size of the view can change dynamically.
2. Dynamic and Static span¶
span has two modes: static and dynamic.
2.1 Static span¶
A static span determines its size at compile time; its Extent is a constant value. When using a static span, we need to know the size of the data at compile time.
For example:
The size of a static span is fixed at compile time, so the compiler can apply certain optimizations, such as omitting bounds checks. But because Extent is a constant, a static span cannot have its size changed at runtime.
2.2 Dynamic span¶
The size of a dynamic span is determined at runtime; Extent defaults to dynamic_extent. This kind of span does not require the size to be known at compile time and can adapt to arrays or containers of any size.
For example:
A dynamic span suits the cases where the size cannot be determined at compile time; it offers greater flexibility.
The implementation in the gcc source is fairly simple: it defaults to a very large integer (the unsigned value of -1), and the real value is computed at construction time.
For example, ranges::size is used here to set the internal size; see the code below.
Setting the dynamic size.
3. subspan¶
span provides a very useful member function, subspan, which lets us extract a sub-view from an existing span without copying data. This is very handy when processing large data sets and avoiding unnecessary memory overhead.
The subspan() method has two overloads:
- One takes
__offsetand__countas parameters, where__offsetis the starting position and__countis the size of the sub-range (defaulting todynamic_extent, i.e. up to the end of the originalspan). - The other takes no parameters and is implemented on top of templates.
constexpr auto
subspan() const noexcept
-> span<element_type, _S_subspan_extent<_Offset, _Count>()> {}
template<size_t _Offset, size_t _Count = dynamic_extent>
[[nodiscard]]
constexpr auto
subspan() const noexcept
-> span<element_type, _S_subspan_extent<_Offset, _Count>()> {}
Example 1:
Example 2:
4. Usage Pitfalls¶
Although span is a very useful tool, there are some common pitfalls to keep in mind when using it:
4.1 Do Not Let a span Refer to Memory That Has Been Freed¶
A span does not own the data; it is only a reference to it. So if the memory a span refers to is released (for example, the original array is destroyed or goes out of scope), accessing that span leads to undefined behavior.
For example:
std::span<int> create_span() {
int arr[5] = {1, 2, 3, 4, 5};
return std::span<int>(arr, 5);
}
// 错误:返回的 span 引用了栈上临时变量,函数返回后 arr 被销毁
auto s = create_span();
To avoid this problem, make sure the data a span refers to stays valid for its lifetime. Consider holding the data in dynamically allocated memory, or making sure the data's lifetime matches the span's.
4.2 Problems When the Underlying Array Changes Size¶
A span's reference to the underlying data is direct, not a copy. When the underlying data changes, the span itself does not notice these changes. Therefore, when the size of the underlying array or container changes (for example, through memory reallocation), the original span may become invalid.
std::vector<int> vec = {1, 2, 3, 4, 5};
std::span<int> s(vec); // s 引用 vec 的数据
// 改变底层容器的大小
vec.push_back(6); // vec 大小变为 6
// 这里 s 仍然引用 vec 的数据,但 vec 可能已经重新分配了内存
// 这可能导致 s 引用失效或未定义行为
Related Topics¶
- Getting Started with C++20 Coroutines | C++那些事
- C++20 channel in Practice | C++那些事
- C++20 Concepts | C++那些事
- C++20 constexpr and the Comma Expression | C++那些事
- C++20 Byte Order (endian) | C++那些事
- C++20 Lambda Expressions (Part 1) | C++那些事
- C++20 Modules | C++那些事
- C++20 Ranges Library | C++那些事
- A Guide to C++20 New Features | C++那些事
- C++20 [[likely]] and [[unlikely]] | C++那些事