C++ std::span | Contiguous Memory View (C++20)
이 글의 핵심
Practical guide to std::span: non-owning contiguous views, APIs, and lifetime rules.
What is span?
std::span is a lightweight view of a contiguous memory region introduced in C++20. It provides a safe, unified interface by providing a size and pointer to an array, vector, or contiguous memory.
#include <span>
void process(std::span<int> data) {
for (int x : data) {
std::cout << x << " ";
}
}
int arr[] = {1, 2, 3};
std::vector<int> vec = {4, 5, 6};
process(arr); // arrangement
process(vec); // vector
Why do you need it?:
- Unified Interface: Treats arrays, vectors, and C arrays as one type.
- Safety: Boundary check possible including size information
- Performance: No copy, just reference (pointer + size)
- Conciseness: No need to pass pointer and size separately
// ❌ Traditional method: pointer + size (inconvenient, possible error)
void process(int* data, size_t size) {
for (size_t i = 0; i < size; ++i) {
std::cout << data[i] << " ";
}
}
int arr[] = {1, 2, 3};
process(arr, 3); // Pass the size manually
// ✅ span: integrated and secure
void process(std::span<int> data) {
for (int x : data) { // range based for available
std::cout << x << " ";
}
}
process(arr); // Size automatic inference
Characteristics of span:
- Non-owning: Does not own the memory, only references it
- Lightweight: Stores only pointer and size (usually 16 bytes)
- Copyable: Copying costs are very low
- View: Original data can be modified (const span is read-only)
std::vector<int> vec = {1, 2, 3, 4, 5};
std::span<int> sp{vec};
sp[0] = 10; // Edit original vec
std::cout << vec[0]; // 10 (edited)
Structure of span:
// Conceptual Implementation
template<typename T>
class span {
T* data_;
size_t size_;
public:
span(T* data, size_t size) : data_(data), size_(size) {}
size_t size() const { return size_; }
T* data() const { return data_; }
T& operator const { return data_[index]; }
// iterator
T* begin() const { return data_; }
T* end() const { return data_ + size_; }
};
Default use
#include <span>
#include <vector>
std::vector<int> v = {1, 2, 3, 4, 5};
// full span
std::span<int> sp1{v};
// partial span
std::span<int> sp2{v.data() + 1, 3}; // {2, 3, 4}
// size
std::cout << sp1.size() << std::endl; // 5
Practical example
Example 1: Function parameters
#include <span>
#include <vector>
#include <array>
// unified interface
void printData(std::span<const int> data) {
for (int x : data) {
std::cout << x << " ";
}
std::cout << std::endl;
}
int main() {
int arr[] = {1, 2, 3};
std::vector<int> vec = {4, 5, 6};
std::array<int, 3> stdArr = {7, 8, 9};
printData(arr); // 1 2 3
printData(vec); // 4 5 6
printData(stdArr); // 7 8 9
}
Example 2: Subrange
#include <span>
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// first 5
std::span<int> first5{v.data(), 5};
// last 5
std::span<int> last5{v.data() + 5, 5};
// subspan
std::span<int> sp{v};
auto middle = sp.subspan(3, 4); // {4, 5, 6, 7}
Example 3: Bounds checking
#include <span>
void safeAccess(std::span<int> data, size_t index) {
// boundary check
if (index < data.size()) {
std::cout << data[index] << std::endl;
} else {
std::cout << "out of range" << std::endl;
}
}
int main() {
std::vector<int> v = {1, 2, 3};
safeAccess(v, 1); // 2
safeAccess(v, 10); // Out of range
}
Example 4: 2D array
#include <span>
void processMatrix(std::span<int> data, size_t rows, size_t cols) {
for (size_t i = 0; i < rows; ++i) {
for (size_t j = 0; j < cols; ++j) {
std::cout << data[i * cols + j] << " ";
}
std::cout << std::endl;
}
}
int main() {
std::vector<int> matrix = {
1, 2, 3,
4, 5, 6,
7, 8, 9
};
processMatrix(matrix, 3, 3);
}
span operation
std::span<int> sp{v};
// size
auto size = sp.size();
auto bytes = sp.size_bytes();
// access
int first = sp.front();
int last = sp.back();
int* ptr = sp.data();
// partial range
auto sub1 = sp.first(3); // first 3
auto sub2 = sp.last(3); // last 3
auto sub3 = sp.subspan(2, 3); // [2] to 3
Frequently occurring problems
Problem 1: Lifespan
// ❌ Dangling
std::span<int> getDanglingSpan() {
std::vector<int> v = {1, 2, 3};
return std::span{v};
// v extinction
}
// ✅ Reference disambiguation
std::span<int> getSpan(std::vector<int>& v) {
return std::span{v};
}
Problem 2: const
std::vector<int> v = {1, 2, 3};
// read only
std::span<const int> sp{v};
// ❌ No editing possible
// sp[0] = 10; // error
// ✅ Modifiable
std::span<int> sp2{v};
sp2[0] = 10;
Issue 3: Size
// fixed size span
std::span<int, 3> fixedSpan{arr};
// dynamic size span
std::span<int> dynamicSpan{vec};
// ❌ Size mismatch
// std::span<int, 5> sp{arr}; // error if arr size is 3
Problem 4: Pointer conversion
int* ptr = getData();
size_t size = getSize();
// ✅ Wrapping with spans
std::span<int> sp{ptr, size};
// safe access
for (int x : sp) {
std::cout << x << " ";
}
span vs pointer
// ❌ Pointer (no size information)
void process(int* data, size_t size) {
for (size_t i = 0; i < size; ++i) {
std::cout << data[i] << " ";
}
}
// ✅ span (including size)
void process(std::span<int> data) {
for (int x : data) {
std::cout << x << " ";
}
}
Practice pattern
Pattern 1: Read-only view
class DataProcessor {
public:
// const span: read-only
double calculateAverage(std::span<const double> data) const {
if (data.empty()) return 0.0;
double sum = 0.0;
for (double value : data) {
sum += value;
}
return sum / data.size();
}
};
// use
std::vector<double> values = {1.0, 2.0, 3.0, 4.0, 5.0};
DataProcessor processor;
double avg = processor.calculateAverage(values);
Pattern 2: Sliding Window
template<typename T>
void processWindows(std::span<T> data, size_t windowSize) {
if (data.size() < windowSize) return;
for (size_t i = 0; i <= data.size() - windowSize; ++i) {
auto window = data.subspan(i, windowSize);
// window processing
std::cout << "Window [" << i << "]: ";
for (const auto& value : window) {
std::cout << value << " ";
}
std::cout << '\n';
}
}
// use
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8};
processWindows(std::span{data}, 3);
Pattern 3: Buffer Wrapper
class Buffer {
std::vector<uint8_t> data_;
public:
Buffer(size_t size) : data_(size) {}
// Full buffer view
std::span<uint8_t> asSpan() {
return std::span{data_};
}
// read-only view
std::span<const uint8_t> asSpan() const {
return std::span{data_};
}
// partial view
std::span<uint8_t> slice(size_t offset, size_t length) {
return std::span{data_}.subspan(offset, length);
}
};
// use
Buffer buffer(1024);
auto view = buffer.asSpan();
view[0] = 0xFF;
FAQ
Q1: What is span?
A: A lightweight view of a contiguous memory region in C++20. Pointers and sizes are provided together to provide a secure and unified interface.
std::vector<int> vec = {1, 2, 3, 4, 5};
std::span<int> sp{vec}; // pointer + size
std::cout << sp.size() << '\n'; // 5
std::cout << sp[0] << '\n'; // 1
Q2: Does span copy data?
A: No. A span is a non-owning view, meaning it only references the original data. Copy cost is very low (only copy pointer + size).
std::vector<int> vec(1000000); // big vector
// Create span: no copy (pointer + size only)
std::span<int> sp{vec};
// span copy: very fast (only copies 16 bytes)
std::span<int> sp2 = sp;
Q3: Where is span used?
A:
- Function parameters: Integrate arrays, vectors, and C arrays
- Partial range: slicing, windowing
- Safe Pointer: Boundary check possible with size information included
// unified interface
void process(std::span<int> data) {
// Array, vector, std::array are all possible
}
int arr[] = {1, 2, 3};
std::vector<int> vec = {4, 5, 6};
std::array<int, 3> stdArr = {7, 8, 9};
process(arr);
process(vec);
process(stdArr);
Q4: Is the span size fixed?
A: Supports both dynamic size and fixed size.
// Dynamic size (default)
std::span<int> dynamicSpan{vec};
// Fixed size (compile time)
std::span<int, 3> fixedSpan{arr};
// Fixed size is verified at compile time
// std::span<int, 5> wrongSpan{arr}; // error: size mismatch
Q5: How do you manage the lifespan of a span?
A: span is a non-owning view, so you need to be careful about the lifetime of the original data.
// ❌ Dangling span
std::span<int> getDanglingSpan() {
std::vector<int> vec = {1, 2, 3};
return std::span{vec}; // vec disappears!
}
// ✅ Safe to use
std::span<int> getSpan(std::vector<int>& vec) {
return std::span{vec}; // vec is owned by the caller
}
Q6: What is the difference between const span and span?
A:
const std::span<int>: span itself is const (cannot point to other memory)std::span<const int>: The data pointed to is const (data cannot be modified)
std::vector<int> vec = {1, 2, 3};
// span<const int>: data read only
std::span<const int> sp1{vec};
// sp1[0] = 10; // Error: Data cannot be modified
sp1 = std::span<const int>{}; // OK: span itself can be modified
// const span<int>: span itself is const
const std::span<int> sp2{vec};
sp2[0] = 10; // OK: Data can be modified
// sp2 = std::span<int>{}; // Error: span itself cannot be modified
Q7: Can span be converted to a pointer?
A: It is possible. You can get a pointer with the data() method.
std::span<int> sp{vec};
// Get pointers
int* ptr = sp.data();
// Legacy API calls
legacyFunction(ptr, sp.size());
Q8: What are span learning resources?
A:
- “C++20 The Complete Guide” by Nicolai Josuttis
- “Effective Modern C++” by Scott Meyers
- cppreference.com - std::span
Related posts: string_view, array, vector.
One-Line Summary: std::span is a lightweight, non-owned view of a contiguous region of memory, providing a safe, unified interface.
Good article to read together (internal link)
Here’s another article related to this topic.
- C++ span | “Array View” C++20 Guide
- C++ Aggregate Initialization | “Aggregate initialization” guide
- C++ subrange | “Subrange” guide
Practical tips
These are tips that can be applied right away in practice.
Debugging tips
- If you run into a problem, check the compiler warnings first.
- Reproduce the problem with a simple test case
Performance Tips
- Don’t optimize without profiling
- Set measurable indicators first
Code review tips
- Check in advance for areas that are frequently pointed out in code reviews.
- Follow your team’s coding conventions
Practical checklist
This is what you need to check when applying this concept in practice.
Before writing code
- Is this technique the best way to solve the current problem?
- Can team members understand and maintain this code?
- Does it meet the performance requirements?
Writing code
- Have you resolved all compiler warnings?
- Have you considered edge cases?
- Is error handling appropriate?
When reviewing code
- Is the intent of the code clear?
- Are there enough test cases?
- Is it documented?
Use this checklist to reduce mistakes and improve code quality.
Keywords covered in this article (related search terms)
This article will be helpful if you search for C++, span, view, array, C++20, etc.
Related articles
- See the entire C++ series
- C++ Adapter Pattern Complete Guide | Interface conversion and compatibility
- C++ ADL |
- C++ Aggregate Initialization |