CodeRigor

FREE LESSON

mdspan: shape, strides & borrowed multidimensional data

Use actual C++23 mdspan with right, left and padded layouts; validate backing spans and overflow, distinguish logical size from storage, and transpose safely.

After span and lifetime and templates, std::mdspan (C++23) extends borrowing to multidimensional coordinates. The installed GCC/libstdc++ supports the real standard type; this lesson does not substitute nested vectors or a custom view.

A handle, extents, mapping and accessor

The data handle identifies existing storage. Extents describe the logical shape; static dimensions live in the type and dynamic dimensions are supplied at runtime. A layout mapping turns coordinates into offsets. An accessor turns the handle and offset into an element reference. The default accessor uses ordinary element access; custom accessors can serve other storage models.

Coordinate access follows the mapping
The mdspan copies borrowing metadata. It does not allocate or extend the lifetime of the original storage.
Coordinate access follows the mapping
The mdspan copies borrowing metadata. It does not allocate or extend the lifetime of the original storage.
C++
#include <mdspan>
#include <array>
#include <cstdio>
int main() {
    std::array<double,6> data{1,2,3,4,5,6};
    std::mdspan<double,std::extents<std::size_t,2,3>> matrix(data.data());
    auto copy=matrix;copy[1,2]=9;
    std::printf("same storage %d, value %.0f\n",copy.data_handle()==matrix.data_handle(),data[5]);
}

A copied view still borrows the same elements. Resizing/reallocating a backing vector can invalidate the handle. const mdspan<double,...> makes the view object const, while its elements remain writable; mdspan<const double,...> provides read-only element access. This is the same distinction between a const pointer and a pointer to const data.

Logical size does not imply a contiguous layout

layout_right makes the rightmost index vary fastest. layout_left makes the leftmost index fastest. layout_stride accepts a unique strided mapping, which can include padding. An mdspan is not inherently a contiguous matrix; do not flatten by size() unless the mapping and accessor support that operation.

The same logical matrix can have different physical order
Always use view[row,column]. Assuming row-major storage gives wrong answers for layout_left.
The same logical matrix can have different physical order
Always use view[row,column]. Assuming row-major storage gives wrong answers for layout_left.

For a two-row, three-column padded view with row stride 5, logical size is 6, but the last element lies at offset 7. The required span is therefore 8, not 6 and not necessarily the full rows×stride allocation. Extents count elements; strides in the default accessor mapping count element offsets, not bytes.

Padded rows contain unused physical slots
Logical elements occupy offsets 0,1,2 and 5,6,7. The caller must supply at least eight accessible elements and preserve padding.
Padded rows contain unused physical slots
Logical elements occupy offsets 0,1,2 and 5,6,7. The caller must supply at least eight accessible elements and preserve padding.
C++
#include <mdspan>
#include <array>
#include <cstdio>
int main() {
    double backing[8]{};
    using View=std::mdspan<double,std::dextents<std::size_t,2>,std::layout_stride>;
    View::mapping_type mapping(std::dextents<std::size_t,2>(2,3),std::array<std::size_t,2>{5,1});
    View padded(backing,mapping);padded[1,2]=42;
    std::printf("logical %zu, required %zu, last %.0f\n",padded.size(),mapping.required_span_size(),backing[7]);
}

Validate before constructing or indexing

The mapping constructor has preconditions; it is not a bounds validator for an arbitrary pointer and shape. The exercise's row-major padded factory accepts positive row_stride >= columns. For nonempty extents it checks (rows-1)*row_stride + columns without overflowing size_t, then checks the claimed accessible storage and null-pointer rule. An empty dimension needs no elements, but stride preconditions still apply.

The caller must actually own the number of accessible elements it claims. mdspan cannot inspect an allocation through a raw pointer. An invalid logical coordinate is still invalid even if the computed offset happens to be inside a larger buffer. Assertions or sanitizers do not replace input validation.

For transpose, validate both output dimensions before writing. The build accepts left/right/padded layouts and requires nonoverlapping logical input/output storage; it does not promise an in-place overlapping transpose. Keep ownership outside these helpers and use bounded small matrices to test indexing, shape, padding and read-only access independently of performance claims.

Watch & read

Free lectures, talks and readings that cover this skill from another angle. All resources →

Mapping logical matrix/image coordinates to physical layouts, with generated-code comparisons and performance caveats.