Table of Contents

In this article, we’ll see details of std::mdspan, a new view type tailored to multidimensional data. We’ll go through type declaration, creation techniques, and options to customize the internal functionality.

Type declaration  

The type is declared in the following way:

template<
    class T,
    class Extents,
    class LayoutPolicy = std::layout_right,
    class AccessorPolicy = std::default_accessor<T>
> class mdspan;

And it has its own header <mdspan>.

The main proposal for this feature can be found at https://wg21.link/P0009

Following the pattern from std::span, we have a few options to create mdspan: with dynamic or static extent.

The key difference is that rather than just one dimension, we can specify multiple. The declaration also gives more options in the form of LayoutPolicy and AccessorPolicy. More on that later.

Static Extent  

std::vector<int> vec = {1, 2, 3, 2, 4, 5, 3, 5, 6, 7, 8, 9};
    
std::mdspan<int, std::extents<size_t, 3, 3>> mat3x3 { vec.data() };
std::print("sizeof: {}, rank: {}, ext 0: {}, ext 1: {}\n",
sizeof(mat3x3), mat3x3.rank(), mat3x3.extent(0), mat3x3.extent(1));

The output:

sizeof: 8, rank: 2, ext 0: 3, ext 1: 3

Run @Compiler Explorer

Dynamic Extent  

And the dynamic option:

std::mdspan<int, std::dextents<size_t, 2>> mat2x2 { vec.data(), 2, 2 };
std::print("sizeof: {}, rank: {}, ext 0: {}, ext 1: {}\n",
sizeof(mat2x2), mat2x2.rank(), mat2x2.extent(0), mat2x2.extent(1));

The output:

sizeof: 24, rank: 2, ext 0: 2, ext 1: 2

Run @Compiler Explorer

The line:

std::mdspan<int, std::dextents<size_t, 2>>

Is equivalent:

std::mdspan<int, std::extents<size_t, std::dynamic_extents, std::dynamic_extents>>

In other words, dextents stands for dynamic extents.

And as of C++26, we can also simplify it with std::dims:

std::mdspan<int, std::dims<2>> mat2x2 { vec.data(), 2, 2 };

Sizeof mdspan  

As you can see, similarly to std::span, when the size is known at compile time, the object can be smaller (it doesn’t have to store the dynamic size).

Run here: @Compiler Explorer

Construction options  

Let’s review some more options to create mdspan:

1. Default Constructor

constexpr mdspan();

Example:

std::mdspan<int, std::dextents<size_t, 1>> empty_span;
std::print("is empty {}\n", empty_span.empty());

See @Compiler Explorer

2. Extents From Variadic Arguments

template< class... OtherIndexTypes >
constexpr explicit mdspan( data_handle_type p, OtherIndexTypes... exts );

Example:

int arr[12] { 0 };
std::mdspan<int, std::extents<size_t, 3, 4>> span(arr);
std::print("w: {}, h: {}\n", span.extent(0), span.extent(1));
auto ex = span.extents();
std::println("{}, {}", ex.rank(), ex.static_extent(1));

std::mdspan<int, std::dextents<size_t, 2>> dspan(arr, 3, 4);
std::print("w: {}, h: {}\n", dspan.extent(0), dspan.extent(1));
auto ex2 = dspan.extents();
std::println("{}, {}", ex2.rank(), ex2.static_extent(1));

Run @Compiler Explorer

3. Extents From std::array

template< class OtherIndexType, std::size_t N >
constexpr explicit(N != rank_dynamic()) mdspan( data_handle_type p, const std::array<OtherIndexType, N>& exts );

Example:

int arr[12] { 0 };
std::array<size_t, 2> my_extents = {3, 4};
std::mdspan<int, std::extents<size_t, 3, 4>> span(arr, my_extents);

or simpler with CTAD:

std::mdspan span(arr, my_extents); // but this time extends are dynamic

Run @Compiler Explorer

Layout  

Since we’re dealing with multidimensional data, there are a couple of ways we can access elements. That’s why we have the “layout” policy class to represent various options.

Here are the basic layouts available as of C++23:

  • layout_right: C or C++ style, row major, where the rightmost index gives stride-1 access to the underlying memory;
  • layout_left: Fortran or Matlab style, column major, where the leftmost index gives stride-1 access to the underlying memory;
  • layout_stride: a generalization of the two layouts above, which stores a separate stride (possibly not one) for each extent.

Plus, as of C++26, we’ll get two more options:

  • layout_left_padded - column-major layout mapping policy with padding stride that can be greater than or equal to the leftmost extent
  • layout_right_padded - row-major layout mapping policy with padding stride that can be greater than or equal to the rightmost extent

Here’s an example:

template <typename layout>
void printMat(std::mdspan<int, std::dextents<size_t, 2>, layout> mat) {
    for (size_t i = 0; i < mat.extent(0); ++i) {
        for (size_t j = 0; j < mat.extent(1); ++j)
            std::print("{} ", mat[i, j]);
        std::println();
    }
}

int main() {
    std::vector<int> vec = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, };

    std::mdspan<int, std::dextents<size_t, 2>, std::layout_right> mat { vec.data(), 2, 3 };
    std::println("right:");
    printMat(mat);

    std::mdspan<int, std::dextents<size_t, 2>, std::layout_left> mat_left { vec.data(), 2, 3 };
    std::println("left:");
    printMat(mat_left);
}

See @Compiler Explorer

The output:

right:
0 1 2 
3 4 5 
left:
0 2 4 
1 3 5 

Summary  

std::mdspan provides a powerful and efficient way to interact with multidimensional data, which was a huge pain point for C++. While sometimes it is hard to spell out the full type, mdspan is very flexible and offers many handy ways to work with data.

In the text, I also showed how to use layout policy for a different way to order elements in the view. The missing part is “AccessorPolicy” which is a policy class that specifies how each element is accessed. For example we can implement a “checked” policy that would ensure the index is not out of range. But I’ll leave that for later considerations.

Back to you

  • Have you played with mdspan?
  • What do you use to work with multidimensional data?

Share your comments below