C++23: A Multidimensional View

A std::mdspan is a non-owning multidimensional view of a contiguous sequence of objects. The contiguous sequence of objects can be a plain C-array, a pointer with a size, a std::array, a std::vector, or a std::string.

Often, this multidimensional view is called a multidimensional array.

The number of dimensions and the size of each dimension determine the shape of the multidimensional array. The number of dimensions is called rank, and the size of each dimension extension. The size of the std::mdspan is the product of all dimensions that are not 0. You can access the elements of a std::mdspan using the multidimensional index operator [].

Each dimension of a std::mdspan can have a static or dynamic extent. static extent means that its length is specified at compile time; dynamic extent means that its length is specified at run time.

Here is its definition:

template<
    class T,
    class Extents,
    class LayoutPolicy = std::layout_right,
    class AccessorPolicy = std::default_accessor<T>
> class mdspan;
  • T: the contiguous sequence of objects
  • Extents: specifies the number of dimensions as their size; each dimension can have a static extent or a dynamic extent
  • LayoutPolicy: specifies the layout policy to access the underlying memory
  • AccessorPolicy: specifies how the underlying elements are referenced

Thanks to class template argument deduction (CTAG) in C++17, the compiler can often automatically deduce the template arguments from the types of initializers:

// mdspan.cpp

#include <mdspan>
#include <iostream>
#include <vector>

int main() {
    
    std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8};          // (1)

    std::mdspan m{myVec.data(), 2, 4};                  // (2)
    std::cout << "m.rank(): " << m.rank() << '\n';      // (4)

    for (std::size_t i = 0; i < m.extent(0); ++i) {     // (6)
        for (std::size_t j = 0; j < m.extent(1); ++j) { // (7)
            std::cout << m[i, j] << ' ';                // (8)
        }
        std::cout << '\n';
    }

    std::cout << '\n';

    std::mdspan m2{myVec.data(), 4, 2};                 // (3)
    std::cout << "m2.rank(): " << m2.rank() << '\n';    // (5)

    for (std::size_t i = 0; i < m2.extent(0); ++i) {
        for (std::size_t j = 0; j < m2.extent(1); ++j) {
        std::cout << m2[i, j] << ' ';  
    }
    std::cout << '\n';
  }

}

I apply class template argument deduction three times in this example. Line (1) uses it for a std::vector, and lines (2) and (3) for a std::mdspan. The first 2-dimensional array m has a shape of (2, 4), the second one m2 a shape of (4, 2). Lines (4) and (5) display the ranks of both std::mdspan. Thanks to the extent of each dimension (lines 6 and 7) and the index operator in line (8), it is straightforward to iterate through multidimensional arrays.

If your multidimensional array should have a static extent, you have to specify the template arguments.

// staticDynamicExtent.cpp

#include <mdspan>
#include <iostream>
#include <string>
#include <vector>
#include <tuple>

int main() {
    
    std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8};

    std::mdspan<int, std::extents<std::size_t, 2, 4>> m{myVec.data()}; // (1)
    std::cout << "m.rank(): " << m.rank() << '\n';

    for (std::size_t i = 0; i < m.extent(0); ++i) {
        for (std::size_t j = 0; j < m.extent(1); ++j) {
            std::cout << m[i, j] << ' ';  
        }
        std::cout << '\n';
    }

    std::cout << '\n';

    std::mdspan<int, std::extents<std::size_t, std::dynamic_extent, 
                std::dynamic_extent>> m2{myVec.data(), 4, 2};          // (2)
    std::cout << "m2.rank(): " << m2.rank() << '\n';

    for (std::size_t i = 0; i < m2.extent(0); ++i) {
        for (std::size_t j = 0; j < m2.extent(1); ++j) {
        std::cout << m2[i, j] << ' ';  
    }
    std::cout << '\n';
  }

   std::cout << '\n';

}

The program staticDynamicExtent.cpp is based on the previous program mdspan.cpp, and produces the same output. The difference is that the std::mdspan m (line 1) has a static extent. For completeness, std::mdspan m2 (line 2) has a dynamic extent. Consequentially, the shape of m is specified with template arguments, but the shape m2 is with function arguments.

Layout Policy

A std::mdspan allows you to specify the layout policy to access the underlying memory. By default, std::layout_right (C, C++, or Python style) is used, but you can also specify std::layout_left (Fortran or MATLAB style). The following graphic exemplifies in which sequence the elements of the std::mdspan are accessed.

 

Rainer D 6 P2 500x500Modernes C++ Mentoring

  • "Fundamentals for C++ Professionals" (open)
  • "Design Patterns and Architectural Patterns with C++" (open)
  • "C++20: Get the Details" (open)
  • "Concurrency with Modern C++" (open)
  • "Generic Programming (Templates) with C++": October 2024
  • "Embedded Programming with Modern C++": October 2024
  • "Clean Code: Best Practices for Modern C++": March 2025
  • Do you want to stay informed: Subscribe.

     

    Traversing two std::mdspan with the layout policy std::layout_right and std::layout_left shows the difference.

    // mdspanLayout.cpp
    
    #include <mdspan>
    #include <iostream>
    #include <vector>
    
    int main() {
        
        std::vector myVec{1, 2, 3, 4, 5, 6, 7, 8};
    
        std::mdspan<int, std::extents<std::size_t,      // (1)
             std::dynamic_extent, std::dynamic_extent>, 
             std::layout_right> m{myVec.data(), 4, 2};
        std::cout << "m.rank(): " << m.rank() << '\n';
    
        for (std::size_t i = 0; i < m.extent(0); ++i) {
            for (std::size_t j = 0; j < m.extent(1); ++j) {
                std::cout << m[i, j] << ' ';  
            }
            std::cout << '\n';
        }
    
        std::cout << '\n';
    
        std::mdspan<int, std::extents<std::size_t,     // (2)
             std::dynamic_extent, std::dynamic_extent>, 
             std::layout_left> m2{myVec.data(), 4, 2};
        std::cout << "m2.rank(): " << m2.rank() << '\n';
    
        for (std::size_t i = 0; i < m2.extent(0); ++i) {
            for (std::size_t j = 0; j < m2.extent(1); ++j) {
            std::cout << m2[i, j] << ' ';  
        }
        std::cout << '\n';
      }
    
    }
    

    The std::mdspan m uses std::layout_right (line 1), the other std::mdspan std::layout_left (line 2). Thanks to class template argument deduction, the constructor call of std::mdspan (line 2) needs no explicit template arguments and is equivalent to the expression std::mdspan m2{myVec.data(), 4, 2}.

    The output of the program shows the two different layout strategies:

    The following table presents an overview of std::mdspan‘s interface.

    Functions of a std::mdspan mdDescription
    md[ind]Access the ind-th element.
    md.sizeReturns the size of the multidimensional array.
    md.rankReturns the dimension of the multidimensional array.
    md.extents(i)Returns the size of the i-th dimension.
    md.data_handleReturns a pointer to the contiguous sequence of elements.

    What’s Next?

    C++20 does not provide concrete coroutines, but C++20 provides a framework for implementing coroutines. This changes with C++23. std::generator is the first concrete coroutine.

    Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Stephen Kelley, Kyle Dean, Tusar Palauri, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, Bhavith C Achar, Marco Parri Empoli, Philipp Lenk, Charles-Jianye Chen, Keith Jeffery, Matt Godbolt, and Honey Sukesan.

    Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.

    My special thanks to Embarcadero
    My special thanks to PVS-Studio
    My special thanks to Tipi.build 
    My special thanks to Take Up Code
    My special thanks to SHAVEDYAKS

    Modernes C++ GmbH

    Modernes C++ Mentoring (English)

    Do you want to stay informed about my mentoring programs? Subscribe Here

    Rainer Grimm
    Yalovastraße 20
    72108 Rottenburg

    Mobil: +49 176 5506 5086
    Mail: schulung@ModernesCpp.de
    Mentoring: www.ModernesCpp.org

    Modernes C++ Mentoring,