std::mdspan
Multi-dimensional array views made simple
📊 What is std::mdspan?
std::mdspan is a C++23 feature that provides a non-owning view over multi-dimensional data. It allows you to work with matrices, tensors, and other multi-dimensional arrays efficiently and safely.
#include <mdspan>
#include <iostream>
#include <vector>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5, 6};
std::mdspan matrix(data.data(), 2, 3); // 2x3 matrix
std::cout << "Element [1,2]: " << matrix[1, 2] << std::endl;
return 0;
}
Output:
Element [1,2]: 6
Key Features of std::mdspan
Non-owning
Views existing data without copying
// No memory allocation
Multi-dimensional
Support for any number of dimensions
mdspan<int, 3, 4, 5>
Flexible Layout
Customizable memory layout patterns
// Row-major, column-major
Zero Overhead
Compile-time optimizations
// No runtime cost
🔹 Basic 2D Matrix
A 2D matrix view provides efficient access to two-dimensional data without copying. Implemented via a class or a span, it maps a one-dimensional underlying array (like a std::vector) into rows and columns using index calculations (index = row * cols + col). This offers a natural interface for algorithms in image processing, linear algebra, and game grids. The view maintains the original data's layout and performance while presenting a convenient 2D abstraction for element access and iteration.
#include <mdspan>
#include <iostream>
#include <vector>
int main() {
// Create data storage
std::vector<int> data = {
1, 2, 3,
4, 5, 6,
7, 8, 9
};
// Create 3x3 matrix view
std::mdspan matrix(data.data(), 3, 3);
// Access elements
std::cout << "Matrix:" << std::endl;
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
std::cout << matrix[i, j] << " ";
}
std::cout << std::endl;
}
return 0;
}
Output:
Matrix: 1 2 3 4 5 6 7 8 9
🔹 Dynamic Dimensions
Handling runtime-determined matrix dimensions requires flexible memory management. The number of rows and columns can be specified at runtime, necessitating dynamic allocation (e.g., std::vector). The matrix view must compute strides correctly to support non-contiguous data if needed. This flexibility is crucial for applications that load data from files or user input, such as scientific simulations, spreadsheet tools, or graphic applications where the data size is not known at compile time.
#include <mdspan>
#include <iostream>
#include <vector>
void print_matrix(std::mdspan<int, std::dynamic_extent, std::dynamic_extent> mat) {
for (size_t i = 0; i < mat.extent(0); ++i) {
for (size_t j = 0; j < mat.extent(1); ++j) {
std::cout << mat[i, j] << " ";
}
std::cout << std::endl;
}
}
int main() {
std::vector<int> data = {10, 20, 30, 40};
// 2x2 matrix
std::mdspan matrix(data.data(), 2, 2);
std::cout << "2x2 Matrix:" << std::endl;
print_matrix(matrix);
return 0;
}
Output:
2x2 Matrix: 10 20 30 40
🔹 3D Tensor Example
A 3D tensor extends the matrix concept to three dimensions for volumetric data. It can be viewed as
a vector of matrices or a single flat array with a more complex indexing scheme
(index = (depth * rows + row) * cols + col). Tensors are fundamental in areas like 3D graphics (voxel
data), multi-channel image processing (e.g., RGB with time), and machine learning (multidimensional batches).
Efficient slicing and subview operations on tensors enable powerful data manipulations without duplication.
#include <mdspan>
#include <iostream>
#include <vector>
int main() {
// 2x3x4 tensor (24 elements)
std::vector<int> data(24);
// Fill with sequential values
std::iota(data.begin(), data.end(), 1);
// Create 3D view
std::mdspan tensor(data.data(), 2, 3, 4);
std::cout << "3D Tensor [0][1][2]: " << tensor[0, 1, 2] << std::endl;
std::cout << "3D Tensor [1][2][3]: " << tensor[1, 2, 3] << std::endl;
// Print dimensions
std::cout << "Dimensions: " << tensor.extent(0) << "x"
<< tensor.extent(1) << "x" << tensor.extent(2) << std::endl;
return 0;
}
Output:
3D Tensor [0][1][2]: 7 3D Tensor [1][2][3]: 24 Dimensions: 2x3x4
🔹 Subviews and Slicing
Subviews and slicing enable efficient access to contiguous or strided subsets of tensor data without
copying. Operations like extracting a specific row, column, or rectangular region from a
multi-dimensional array return a lightweight view object that references the original data. For example, a 3D tensor
of shape (4, 5, 6) can be sliced to extract a 2×3 subregion starting at index (1, 2, 1) using
tensor.slice({1, 2, 1}, {2, 3, 4}), returning a view with shape (2, 3, 4). Slices are defined using
start indices, strides, and element counts for precise control. This capability is essential for
performance-critical numerical computing, image processing tasks like region-of-interest extraction, and
implementing algorithms that partition and manipulate large datasets efficiently without memory overhead.
#include <mdspan>
#include <iostream>
#include <vector>
int main() {
std::vector<int> data = {
1, 2, 3, 4,
5, 6, 7, 8,
9, 10, 11, 12
};
// Original 3x4 matrix
std::mdspan matrix(data.data(), 3, 4);
// Create subview (first 2 rows, first 3 columns)
auto subview = std::submdspan(matrix,
std::pair{0, 2},
std::pair{0, 3});
std::cout << "Subview:" << std::endl;
for (size_t i = 0; i < subview.extent(0); ++i) {
for (size_t j = 0; j < subview.extent(1); ++j) {
std::cout << subview[i, j] << " ";
}
std::cout << std::endl;
}
return 0;
}
Output:
Subview: 1 2 3 5 6 7