`MDSPAN`

 Document #: P0009r14 Date: 2022-02-14 Project: Programming Language C++ LEWG Reply-to: Christian Trott D.S. Hollman Damien Lebrun-Grandie Mark Hoemmen Daniel Sunderland H. Carter Edwards Bryce Adelstein Lelbach Mauro Bianco Ben Sander Athanasios Iliopoulos<> John Michopoulos<> Nevin Liber

Special thanks to Tomasz Kaminski for invaluable help in preparing this paper for wording review.

1 Revision History

1.1 P0009r15: 2022-02 Mailing

1.1.0.1 Changes from R14

• `mdspan::rank[_dynamic]` returns `size_t`

• fix comparison operator for `layout_stride` to take strides into account.

• fix `layout_stride` mapping `required_span_size`

• Consistently use “`<`expr`>` is `true`” instead of “`<`expr`>` is true”.

• In the layout mappings’ `operator()` Effects clauses, use only `index_sequence`and fix syntax error in `stride(P())`.

• Replace various unary folds with binary to handle `extents<>`.

• Consistently use `Extents::rank()` in layout mapping wording.

• Typo in `mdspan::mapping_type`: `template mapping_type` should be `template mapping`.

• `mdspan`’s `array` constructor calls `Extents`’s `array` constructor.

• Clarify the value of `static_stride` for `submdspan`’s result.

• Significant editorial changes based on LWG small-group review

1.2 P0009r14: 2021-11 Mailing

1.2.0.1 LEWG Review 11/01/2021

• ACTION: Maybe a default constructible accessor should imply default constructible spans. Default constructible spans are important in many cases.
• done
• ACTION: Make extents conditionally explicit when converting from dynamic to static extents.
• done
• ACTION: Make layout and mdspan converting constructors conditionally explicit based upon the underlying types
• done
• ACTION: constructing extents from std::array needs a deduction guide?
• not done: you can’t do this since you can’t use alias and you can’t return a parameter pack
• QUESTION: Extents parameter pack ctor needs to be explicit? Deduction guide needs to be explicit?
• QUESTION: submdspan constructor can take tuple<size_t, size_t> and get pair<size_t, size_t> for free (could add specific type for submdspan, so long as it can be converted from pair and tuple).
• we changed submdspan to take things convertible to `tuple<size_t,size_t>`
• ACTION: needs feature test macro
• done
• QUESTION: Can get rid of mdspan ctor takes pointer + array? Can construct extent from array. Disagreement on authors on this one.
• not done: would remove ctad from array
• ACTION: Explore modifying the requirements on which extents need to be provided when constructing an mdspan or extents object.
• done
• Authors decided to enable construction from both dynamic extents only, and all extents (for both integer packs and arrays)
• Why from dynamic extents only:
• no redundant information
• precondition free constructor
• enables fully static extents mdspan construction from ptr only
• Why from all extents:
• no confusion what extent a given argument is associated with

• enables easier writing of certain types of generic code e.g.:

``````template<class mds1_t, class mds2_t>
auto alloc_gemm_result(mds1_t mdspan1, mds2_t mdspan2) {
using return_t = mdspan<double,
Extents< mds1_t::extents_type::static_extent<0>,
mds2_t::extents_type::static_extent<1>>;
double* ptr = new double[mdspan1.extent(0)*mdspan2.extent(1)];
return return_t(ptr, mdspan1.extent(0),mdspan2.extent(1));
}``````

1.2.0.2 Changes from R13

• changes to harmonize with `std::span`
• made `extents` converting constructor conditionally explicit, for cases where dynamic extents are turned into static extents
• made convertibility of `default_accessor` depend on convertibility of `element_type(*)[]` instead of `pointer` to prevent derived class to base class assignment
• remove converting assignment operators throughout
• made layout mapping converting constructors conditionally explicit, depending on `extents` being not implicitly convertible
• made `mdspan` converting constructor conditionally explicit, for cases where any of the exposition only members or the template parameters are only explicitly convertible
• Improve submdspan wording
• the wording defines more clearly how the submdspan is constructed, not just through ensures
• made layout wording style consistent
• don’t require default constructibility from accessors and mappings (still require it for pointer though)
• fixed layout_stride conversion construction
• made deduction guide from integers for extents/mdspan explicit
• tweaked constraints on mdspan to not include element type and the full extents
• left pointer, since mdspan converts those in its converting constructor
• also left some specific constraints regarding extents to prevent custom layouts from changing rank or assigning different sized static extents
• accept `tuple` instead of `pair` for subslice arguments in `submdspan`.
• remove `mdspan::unique_size`
• fix `layout_stride` constructor to be flexible with integral types of strides array
• make `extents` and `mdspan` constructors accept either `rank_dynamic` or `rank` integer arguments (or an `array` of that size)
• remove mdspan trivially default constructible clause: it never is because we value initialize pointer inline
• remove nonowning word from mdspan description: there is not really a reason to have it. Would allow `shared_ptr` as `pointer`
• allow conversion for 1D `layout_left` to `layout_right` and vice versa
• allow implicit conversion for rank-0 `layout_left`, `layout_right`, and `layout_stride` to each other

1.3 P0009r13: 2021-10 Mailing

LEWG reviewed P0009r12 together with P2299r3 on 2021-06-08.

LEWG Poll Approve the direction of P2299R3 and merge it into P0009.

SF F N A SA
12 6 0 1 0

Attendance: 25; Number of authors: 1 [presumably for P2299, as P0009 coauthors were also attending]; Author’s Position: SF.

• Incorporated changes proposed by P2299r3
• Added `dextents` alias
• Removed `mdspan` alias and renamed `basic_mdspan` to `mdspan` (which is now a class type, not an alias). This undoes a change introduced in P0009r6. P2299r3 explains the rationale. Existing code using the `mdspan` alias will need to change by replacing the list of extents template arguments with a single `extents` type.
• Added `mdspan` deduction guides
• As needed for deduction guides, added `layout_type` alias to layout mapping requirements and to `layout_left`, `layout_right`, and `layout_stride`
• Adapted new LWG wording guidelines by replacing “Expects” with “Preconditions”
• Minor formatting corrections
• Added design discussion as requested by LEWG
• Remove unconditional `noexcept` from `mdspan`
• Fix layout `required_span_size` for rank-0 mdspan
• Fix `layout_stride::required_span_size` for mdspans with at least one extent being zero
• Use `operator[]` in `mdspan` for multidimensional array access, and add explanation to Discussion section
• Remove reference to `span` in the `mdspan` wording, since `mdspan` does not necessarily require a backing `span` (because `pointer` need not be `ElementType*`)
• added conversion constructor for strided and unique layouts to `layout_stride`
• added constructor for `mdspan` from `pointer` and `extents`
• added requirement for layout policy mapping to be nothrow move constructible and assignable
• added requirement for accessor policy to be nothrow move constructible and assignable
• added requirement for accessor policy pointer to be nothrow move constructible and assignable
• remove throws nothing clauses from mdspan and submdspan.

1.4 P0009r12: post 2021-05 Mailing

• Fixed definition of `static_extent`
• Added converting constructor for `default_accessor` (when the pointers to elements are convertible) for things like `default_accessor<double>` to `default_accessor<const double>`
• Changed [mdspan.accessor.basic] to [mdspan.accessor.default], to correspond with the name change in P0009r11
• Minor formatting corrections

1.5 P0009r11: 2021-05 Mailing

• Ask LEWG to poll on targeting P0009 for C++23
• Change all the sizes from `ptrdiff_t` to `size_t` and `index_type` to `size_type`, for consistency with `span` and the rest of the standard library`
• Renamed `IndexType` to `SizeType` or `SizeTypes` (depending if it is a single type or a parameter pack)
• Changed comparisons to hidden friends
• Explicitly mention which types are trivially copyable or empty. This is important as a major intended use case for this is heterogeneous computing. A trivially copyable is heavily used as a proxy for types which can be copied between a host (such as a CPU) and a device (such as a GPU) or between two devices by just copying the bytes which make up the object representation of the type. If they are not trivially copyable, heterogeneous computing would not be able to use these types as vocabulary types.
• State the conditions that make `basic_mdspan` trivially default constructible
• In `layout_*` types, made `operator()` and `stride()` constexpr
• In `layout_stride`, made assignment operators and `required_span_size()` constexpr to match the other `layout_*` types
• Renamed `subspan` to `submdspan`, as this only applies to `mdspan`
• Made `submdspan()` constexpr
• Tweak the wording of `is_strided`
• Renamed `all_type` to `full_extent_t` and `all` to `full_extent`
• Renamed `accessor_basic` to `default_accessor`
• Removed accessor policy `decay(p)` member function as it was an artifact from an earlier version of this proposal when `basic_mdspan` had a `span()` member function that returned a `std::span`
• Removed `span()` from [mdspan.basic.members] description as `.span()` was removed from an earlier version of this proposal

1.6 P0009r10: Pre 2020-02-Prague Mailing

• Switched to mpark/wg21 pandoc format
• Add general description of span and mdspan
• Removed `mdspan_subspan` expo only type; use `basic_mdspan<`see below`>` instead
• Fixed typos in accessor table
• Made editorial changes to wording based on San Diego feedback
• Updated operational semantics subsection heading based on new style guidelines

1.8 P0009r8: Pre 2018-11-SanDiego Mailing

• Refinement based upon updated prototype / reference implementation

1.9 P0009r7: Post 2018-06-Rapperswil Mailing

• wording reworked based on guidance: LWG review at 2018-06-Rapperswil
• usage of `span` requires reference to C++20 working draft
• namespace for library TS `std::experimental::fundamentals_v3`

1.10 P0009r6 : Pre 2018-06-Rapperswil Mailing

P0009r5 was not taken up at 2018-03-Jacksonville meeting. Related LEWG review of P0900 at 2018-03-Jacksonville meeting

LEWG Poll We want the ability to customize the access to elements of span (ability to restrict, etc):

``span<T, N, Accessor=...>``
SF F N A SA
1 1 1 2 8

LEWG Poll We want the customization of `basic_mdspan` to be two concepts `Mapper` and `Accessor` (akin to `Allocator` design).

``````basic_mdspan<T, Extents, Mapper, Accessor>
mdspan<T, N...>``````
SF F N A SA
3 4 5 1 0

LEWG Poll: We want the customization of `basic_mdspan` to be an arbitrary (and potentially user-extensible) list of properties.

``basic_mdspan<T, Extents, Properties...>``
SF F N A SA
1 2 2 6 2

Changes from P0009r5 due to related LEWG reviews:

• Replaced variadic property list with extents, layout mapping, and accessor properties.
• Incorporated P0454r1.
• Renamed `mdspan` to `basic_mdspan`.
• Added a `mdspan` alias to `basic_mdspan`.

1.11 P0009r5 : Pre 2018-03-Jacksonville Mailing

LEWG review of P0009r4 at 2017-11-Albuquerque meeting

LEWG Poll: We should be able to index with `span<int type[N]>` (in addition to array).

SF F N A SA
2 11 1 1 0

Against comment - there is not a proven needs for this feature.

LEWG Poll: We should be able to index with 1d `mdspan`.

SF F N A SA
0 8 7 0 0

LEWG Poll: We should put the requirement on “rank() <= N” back to “rank()==N”.

Unanimous consent

LEWG Poll: With the editorial changes from small group, plus the above polls, forward this to LWG for Fundamentals v3.

Unanimous consent

Changes from P0009r4:

• Removed nullptr constructor.
• Added constexpr to indexing operator.
• Indexing operator requires that `rank()==sizeof...(indices)`.
• Fixed typos in examples and moved them to appendix.
• Converted note on how extentions to access properties may cause reference to be a proxy type to an “see below” to make it normative.

1.12 P0009r4 : Pre 2017-11-Albuquerque Mailing

LEWG review at 2017-03-Kona meeting

LEWG review of P0546r1 at 2017-03-Kona meeting

LEWG Poll: Should we have a single template that covers both single and multi-dimensional spans?

SF F N A SA
1 6 2 6 3

Changes from P0009r3:

• Align with P0122r5 span proposal.
• Rename to `mdspan`, multidimensional span, to align with `span`.
• Move preferred array extents mechanism to appendix.
• Expose codomain as a `span`.

1.13 P0009r3 : Post 2016-06-Oulu Mailing

LEWG review at 2016-06-Oulu

LEWG did not like the name `array_ref`, and suggested the following alternatives: - `sci_span` - `numeric_span` - `multidimensional_span` - `multidim_span` - `mdspan` - `md_span` - `vla_span` - `multispan` - `multi_span`

LEWG Poll: Are member `begin()`/`end()` still good?

SF F N A SA
0 2 4 3 1

LEWG Poll: Want this proposal to provide range-producing functions outside `array_ref`?

SF F N A SA
0 1 3 2 3

LEWG Poll: Want a separate proposal to explore iteration design space?

SF F N A SA
9 1 0 0 0

Changes from P0009r2:

• Removed iterator support; a future paper will be written on the subject.
• Noted difference between multidimensional array versus language’s array-of-array-of-array…
• Clearly describe requirements for the embedded type aliases (`element_type`, `reference`, etc).
• Expanded description of how the variadic properties list would work.
• Stopped allowing `array_ref<T[N]>` in addition to `array_ref<extents<N>>`.
• Clarified domain, codomain, and domain -> codomain mapping specifications.
• Consistently use extent and extents for the multidimensional index space.

1.14 P0009r2 : Pre 2016-06-Oulu Mailing

Changes from P0009r1:

• Adding details for extensibility of layout mapping.
• Move motivation, examples, and relaxed incomplete array type proposal to separate papers.

1.15 P0009r1 : Pre 2016-02-Jacksonville Mailing

LEWG Poll: What should this feature be called?

Name #
`view` 5
`span` 9
`array_ref` 6
`slice` 6
`array_view` 6
`ref` 0
`array_span` 7
`basic_span` 1
`object_span` 3
`field` 0

LEWG Poll: Do we want 0-length static extents?

SF F N A SA
3 4 2 3 0

LEWG POLL: Do we want the language to support syntaxes like `X[3][][][5]`?

Syntax #
`view<int[3][0][][5], property1>` 12
```view<int, dimension<3, 0, dynamic_extent, 5>, property1>``` 4
`view<int[3][0][dynamic_extent][5], property1>` 5
`view<int, 3, 0, dynamic_extent, 5, property1>` 4
```view<int, 3, 0, dynamic_extent, 5, properties<property1>>``` 2
```view<arr<int, 3, 0, dynamic_extent, 5>, property1>``` 4
`view<int[3][0][][5], properties<property1>>` 9

LEWG POLL: Do we want the variadic property list in template args (either raw or in `properties<>`)? Note there is no precedence for this in the library.

SF F N A SA
3 6 3 0 0

LEWG POLL: Do we want the per-view bounds-checking knob?

SF F N A SA
3 4 1 2 1

Changes from P0009r0:

• Renamed `view` to `array_ref`.
• How are users allowed to add properties? Needs elaboration in paper.
• `view<int[][][]>::layout` should be named.
• Rename `is_regular` (possibly to `is_affine`) to avoid overloading the term with the `Regular` concept.
• Make static span(), operator(), constructor, etc variadic.
• Demonstrate the need for improper access in the paper.
• In `operator()`, take integral types by value.

1.16 P0009r0 : Pre 2015-10-Kona Mailing

Original non-owning multidimensional array reference (`view`) paper with motivation, specification, and examples.

LEWG Poll: `span` should specify the dynamic extent as the element type of the first template parameter rather than the (current) second template parameter

SF F N A SA
5 3 2 2 0

LEWG Poll: `span` should support the addition of access properties variadic template parameters

SF F N A SA
0 10 1 5 0

Authors agreed to bring a separate paper ([[P0900r0]]) discussing how the variadic properties will work.

2 Description

2.1 What we propose to add

This paper proposes adding to the C++ Standard Library a multidimensional array view, `mdspan`, along with classes, class templates, and constants for describing and creating multidimensional array views. It also proposes adding the `submdspan` function that “slices” (returns an `mdspan` that views a subset of) an existing mdspan`.

The `mdspan` class template can represent arbitrary mixes of compile-time or run-time extents. Its element type can be any complete object type that is neither an abstract class type nor an array type. It has two customization opportunities for users: the layout mapping and the accessor. The layout mapping specifies the formula, and properties of the formula, for mapping a multidimensional index to an element of the array. The accessor governs how elements are read and written.

2.2 Definitions

A multidimensional array view views a multidimensional array, just as a `span` views a one-dimensional `array` or `vector`.

A multidimensional array of rank $R$ maps from a tuple of $R$ indices to a single offset index. Each of the $R$ indices in the tuple is in a bounded range whose inclusive lower bound is zero, and whose nonnegative exclusive upper bound is that index’s extent. The array thus has $R$ extents. The offset index ranges over a subset of a bounded contiguous index range whose lower bound is zero, and whose upper bound is the product of the $R$ extents.

More formally, a multidimensional array of rank $R$ maps from its domain, a multidimensional index space of rank $R$, to its codomain, a set of objects accessible from a contiguous range of integer indices. A multidimensional index space of rank $R$ is the Cartesian product $\left[0, N$0) ⨯ [0, N1) ⨯ … ⨯ [0, NR-1) of half-open integer intervals, where the $N$k for $k = 0$, …, $R-1$ are the array’s extents. A multidimensional index is a element of a multidimensional index space.

2.3 Why do we need multidimensional arrays?

Multidimensional arrays are fundamental concepts in many fields, including graphics, mathematics, statistics, engineering, and the sciences. Many programming languages thus come with multidimensional array data structures either as a core language feature, or as a tightly integrated standard library. Example languages include Ada, ANSI Common Lisp, APL, C#, Fortran, Julia, Matlab, Mathematica, Pascal, Python (via NumPy), and Visual Basic. The original version of the Fortran language for the IBM 704 featured arrays with one, two, or three extents (Backus 1956, pp. 10-11).

Multidimensional arrays have long been useful for representing large amounts of data, describing points in physical space, or expressing approximations of functions. They are a natural way to represent mathematical objects like matrices and tensors. This makes multidimensional arrays a critical data structure for many computations at the heart of modern machine learning. In fact, one of the predominant machine learning frameworks is called TensorFlow.

2.4 Why are existing C++ data structures not enough?

C++ currently has the following approaches that could be used to represent multidimensional arrays:

1. “native” arrays where all the extents are compile-time constants, like `int[3][4][5]`;

2. pointer-of-pointers(-of-pointers…), like `int***`, set up as a data structure to view multidimensional data;

3. arrays-of-arrays(-of-arrays…) data structures, like `vector<vector<array<int, N>>>`; or

4. `gslice`, which selects a subset of indices of a `valarray` and can be used to impose a multidimensional array layout on the `valarray`, in a way analogous to `layout_stride`.

If a multidimensional array has any extents that are not known at compile time, Approach (1) does not work.

Approach (2) does not suffice as a stand-alone data structure, because a pointer-of-pointers does not carry along the array’s run-time extents. Users thus end up building some subset of `mdspan`’s functionality to represent a multidimensional array view. Every run-time extent other than the rightmost requires a separate memory allocation for an array of pointers. A pointer-of-pointers also loses information about any dimensions known at compile time. Users cannot arbitrarily mix compile-time and run-time extents.

Approach (3) can mix `vector` and `array` to represent extents known at run time resp. compile time. However, any use of `vector` at any position other than the outermost results in the data structure no longer having a contiguous memory allocation (or a subset thereof) for the elements. This makes the data structure incompatible with many libraries that expect a subset of a contiguous allocation. Also, every run-time extent other than the rightmost requires a separate memory allocation for an array of arrays. In addition, each element access requires reading multiple memory locations (“pointer chasing”). Finally, the inlining depth for an element access is proportional to the array’s rank.

Approach (4) is meant for addressing many elements of a `valarray` all at once. Even though `valarray` itself is a one-dimensional array, one can use `gslice` to make the `valarray` represent multidimensional data. Giving a `gslice` to `valarray::operator[]` returns something that references a subset of elements of the original `valarray`. However, the result (a `gslice_array` in the nonconst case, some type that might be an expression template in the const case) is not guaranteed to have an `operator[]`. Thus, it’s not a view, whereas our proposed `submdspan` function always takes and returns a view. In the const case, the result might even be a (deep) copy of the input. Finally, `gslice` offers no efficient way to address a single element. The `gslice` constructor takes strides and lengths as `valarray`s and is meant for array-based computation. Accessing a single element requires accessing the memory of three `valarray`s.

2.5 Mixing compile-time and run-time extents

The fundamental reason to allow expressing extents at compile time is performance. Knowing an extent at compile time enables many compiler optimizations, such as unrolling and precomputing of offsets. These can significantly improve the generated code. Not storing extents at run time may help conserve registers and stack space.

In many fields, some extents are naturally known at compile time. For many physics and engineering algorithms, some extents are dictated by fundamental properties of the physical world or the discretization scheme. For example, the position of a particle in space requires a rank-3 array, since physical space has three dimensions. At the same time, other extents are only known at run time, such as the number of particles in a simulation. A natural data structure for storing a list of particles would thus be a rank-2 array, where the one run-time extent is the number of particles and the one compile-time extent is three. In graphics, some of the most fundamental objects are square matrices with 2, 3, or 4 rows and columns. The number of matrices with which one would like to compute might only be known at run time. This would make a rank-3 array with two compile-time extents a natural data structure for the matrices.

2.6 Why custom memory layouts?

Our `mdspan` class template permits custom layouts. Our proposal comes with three memory layouts:

• `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.

“Custom” layouts besides these could include space-filling curves or “tiled” layouts.

An important reason we allow different layouts is language interoperability. For example, C++ and Fortran have different “native” layouts. Python’s NumPy arrays have a configurable layout, to provide compatibility with both languages.

Control of the layout can also be used to write code that performs well on different computer architectures when only changing a template argument. Consider the following implementation of a parallel dense matrix-vector product.

``````using layout = /* see-below */;

std::mdspan<double, std::extents<N, M>, layout> A = ...;
std::mdspan<double, std::extents<N>> y = ...;
std::mdspan<double, std::extents<M>> x = ...;

std::ranges::iota_view range{0, N};

std::for_each(std::execution::par_unseq,
std::ranges::begin(range), std::ranges::end(range),
[=](int i) {
double sum = 0.0;
for(int j = 0; j < M; ++j) {
sum += A[i, j] * x[j];
}
y[i] = sum;
});``````

On conventional CPU architectures, this code performs well with `layout = layout_right`, the native C++ row-major layout. However, when offloading the `for_each` to NVIDIA GPUs (which NVIDIA’s `nvc++` compiler can do), `layout = layout_left` (Fortran’s column-major layout) performs much better, since it enables coalesced data access on the matrix `A`.

However, it is not enough to have just C++ and Fortran memory mappings. For instance, one way to compute tensor products is to decompose them into many matrix-matrix multiplications. The resulting decomposition may involve matrices with non-unit strides in both extents. This means that they have neither a row-major nor a column-major layout.

More complex layouts can improve performance significantly for some algorithms. For instance, tiling (a “matrix of small matrices” layout) can improve data locality for many computations relevant to linear algebra and the discretization of partial differential equations. Tiled layouts can also improve vectorization. For example, Intel’s Math Kernel Library introduced the Vectorized Compact Routines. These provide “batched” matrix operations that increase available parallelism by operating on many matrices at once. The Vectorized Compact Routines accept matrices in an “interleaved” layout that optimizes vectorized memory access.

Another design goal for our custom layouts is to permit nonunique layouts. A nonunique layout lets multiple index tuples refer to the same element. This can save memory for data structures that have natural symmetry. For example, if `A` is a symmetric matrix, then `A[i,j]` and `A[j,i]` refer to the same element, so the element can and should only be stored once.

2.7 Why custom accessors?

Custom accessors can provide information to the compiler, or permit the injection of special ways of doing data access. Most hardware today has more ways to access data than simple reads and writes. For example, some instructions affect caching behavior, by making loads and/or stores nontemporal (not cached at some level) or even noncoherent. Other instructions implement atomic access. This is why several of us proposed `atomic_ref`, as the heart of an “atomic accessor” for `mdspan`. C’s `restrict` qualifier conveys whether an array is assumed never to alias another array in some context. The `volatile` keyword is yet another qualifier which limits compiler optimizations around data access. Custom `mdspan` accessors can apply `restrict` (if the C++ implementation supports this extension) or `volatile` to array accesses.

Custom accessors also address concerns relating to heterogeneous memory. Standard C++ does not have the idea of “memory spaces that normal code cannot access,” but many extensions to C++ do have this idea. For example, a custom accessor could convey accessibility by CPU or GPU threads, so that the compiler would prevent users from accessing GPU memory while running on the CPU, or vice versa. Multiple memory spaces occur in programming models other than for GPUs. For example, “partitioned global address space” models have a “global shared memory” that requires special operations to access. C++ libraries like Kokkos expose access to such memory using an analog of a custom accessor. Other accessors could expose an array interface to a persistent storage device that is not directly byte addressable. We do not propose such accessors here, but this is a customization point third-party libraries could directly use, and is available for any future extensions of the C++ standard for supporting heterogeneous memory.

For a discussion of the idea of accessors and several examples, please see (Keryell and Falcou 2016).

2.8 Subspan Support

A critical feature of this proposal is `submdspan`, the subspan or “slicing” function that returns a view of a subset of an existing `mdspan`. The result may have any rank up to and including the rank of the input. All of the aforementioned languages with multidimensional array support provide subspan capabilities. Subspans are important because they enable code reuse. For example, the inner loop in the dense matrix-vector product described above actually represents a dot product – an inner product of two vectors. If one already has a function for such an inner product, then a natural implementation would simply reuse that function. The LAPACK linear algebra library depends on subspan reuse for the performance of its one-sided “blocked” matrix factorizations (Cholesky, LU, and QR). These factorizations reuse textbook non-blocked algorithms by calling them on groups of contiguous columns at a time. This lets LAPACK spend as much time in dense matrix-matrix multiply (or algorithms with analogous performance) as possible.

2.9 Why propose a multidimensional array view before a container?

Factoring views from containers generally makes sense. For example, one often sees functions that take `vector` by reference when they only need to access the `vector`’s elements or call `.size()` on it. This is one reason for `span`. Some of us have proposed a multidimensional array container, `mdarray` P1684, but we have focused on `mdspan` because we consider views more fundamental.

Many fields that compute with multidimensional arrays rely heavily on shared-memory parallel programming, where multiple processing units (threads, vector units, etc.) access different elements of the same array in parallel. Memory allocation and deallocation are “synchronization points” for parallel processing units, and thus hinder parallelization. This makes just viewing a multidimensional array, rather than managing its ownership, the most fundamental way for parallel computations to express how they access an array.

It is often necessary to view previously allocated memory as a multidimensional array. An important special case is when C++ code is calling or being called from another programming language, such as C, Fortran, or Python. This use case matters enough to Python that its C API defines a Buffer Protocol for viewing multidimensional arrays across languages. Language interoperability is key to the success of the various Python-based data analysis frameworks built up around NumPy.

2.10 Use multiple-parameter operator[] for array access

We welcome multiple-parameter `operator[]` as the preferred multidimensional array access operator. P1161R3, now part of C++20, prepared the way for this by deprecating comma expressions inside `operator[]` invocations. P2128R6, which proposed changing `operator[]` to accept multiple parameters, was approved at the October 2021 WG21 Plenary meeting. Please refer to P2128 for an extensive discussion.

Many existing libraries use the function call `operator()` for multidimensional array access, with `operator[]` available for rank-1 (single-dimensional) `mdspan`. P2128 gives examples. It’s straightforward to adapt these libraries to transition to `mdspan`. For example, a subclass or wrapper of `mdspan` can provide an `operator()` that simply forwards to `mdspan::operator[]`. The subclass or wrapper can then deprecate `operator()` to help developers find and change all the code that uses it.

2.11 Reference Implementation

A reference implementation of this proposal under BSD license is available at: mdspan. This implementation is also available on godbolt for experimentation: godbolt.

2.12 References

• J. W. Backus et al. “Programmer’s Reference Manual: Fortran Automatic Coding System for the IBM 704.” Applied Science Division and Programming Research Department, International Business Machines Corporation, Oct. 15, 1956. Available online (last accessed Oct. 10, 2021).

• D. Hollman, C. Trott, M. Hoemmen, and D. Sunderland. “`mdarray`: An Owning Multidimensional Array Analog of `mdspan`.” P1684r0, May 28, 2019. Available online (last accessed Oct. 10, 2021).

• R. Keryell and J. Falcou. “Accessors: A C++ standard library class to qualify data accesses.” P0367r0, May 29, 2016. Available online (last accessed Oct. 10, 2021).

3 Editing Notes

The proposed changes are relative to the working draft of the standard as of N4842.

The � character is used to denote a placeholder section number, table number, or paragraph number which the editor shall determine.

Add the header `<mdspan>` to the “C++ library headers” table in [headers] in a place that respects the table’s current alphabetic order.

Add the header `<mdspan>` to the “Containers library summary” table in [containers.general] below the listing for `<span>`.

4 Wording

The � character is used to denote a placeholder section number which the editor shall determine.

``#define __cpp_lib_mdspan YYYYMML // also in <mdspan>``

1 Adjust the placeholder value as needed so as to denote this proposal’s date of adoption.

Make the following changes to 22.7.1 [views.general],

2 The header `<span>` defines the view span. The header `<mdspan>` defines the class template `mdspan` and other facilities for interacting with these multidimensional views.

Add the following subclauses to the end of the [views] subclause (after `span`):

22.7.� Header `<mdspan>` synopsis [mdspan.syn]

``````namespace std {
// [mdspan.extents], class template extents
template<size_t... Extents>
class extents;

template<size_t Rank>
using dextents = see below;

// [mdspan.layout], Layout mapping policies
class layout_left;
class layout_right;
class layout_stride;

// [mdspan.accessor.default]
template<class ElementType>
class default_accessor;

// [mdspan.mdspan], class template mdspan
template<class ElementType, class Extents, class LayoutPolicy = layout_right,
class AccessorPolicy = default_accessor<ElementType>>
class mdspan;

// [mdspan.submdspan]
template<class ElementType, class Extents, class LayoutPolicy,
class AccessorPolicy, class... SliceSpecifiers>
constexpr auto submdspan(
const mdspan<ElementType, Extents, LayoutPolicy, AccessorPolicy>&,
SliceSpecifiers...) -> see below;

// tag supporting submdspan
struct full_extent_t { explicit full_extent_t() = default; };
inline constexpr full_extent_t full_extent{};
}``````

22.7.� Overview [mdspan.terms]

1 A multidimensional index space is a Cartesian product of integer intervals. Each interval can be represented by a half-open range [Ib, Ie), where Ib and Ie are the lower and upper bounds of the ith dimension. The rank of a multidimensional index space is the number of intervals it represents.

2 A pack of integers `idx...` is a multidimensional index into a multidimensional index space S (or representation thereof) if both of following are true:

• (2.1) `sizeof...(idx)` is equal to rank of S, and

• (2.2) For all i in the range [0,rank), the ith value of `idx` is an integer in the interval [Ib, Ie) of S.

3 An integer `r` is a rank index of an index space S (or representation thereof) if r is in the range [0,rank).

22.7.� Class template `extents` [mdspan.extents]

22.7.�.1 Overview [mdspan.extents.overview]

1 The class template `extents` represents a multidimensional index space of rank equal to `sizeof...(Extents)`.

``````namespace std {

template<size_t... Extents>
class extents {
public:
using size_type = size_t;

// [mdspan.extents.cons], Constructors and assignment
constexpr extents() noexcept = default;
constexpr extents(const extents&) noexcept = default;
constexpr extents& operator=(const extents&) noexcept = default;

template<size_t... OtherExtents>
explicit(see below)
constexpr extents(const extents<OtherExtents...>&) noexcept;
template<class... SizeTypes>
explicit constexpr extents(SizeTypes...) noexcept;
template<class SizeType, size_t N>
explicit(N != rank_dynamic())
constexpr extents(const array<SizeType, N>&) noexcept;

// [mdspan.extents.obs], Observers of the domain multidimensional index space
static constexpr size_t rank() noexcept { return sizeof...(Extents); }
static constexpr size_t rank_dynamic() noexcept
{ return ((Extents == dynamic_extent) + ... + 0); }
static constexpr size_type static_extent(size_t) noexcept;
constexpr size_type extent(size_t) const noexcept;

// [mdspan.extents.compare], extents comparison operators
template<size_t... OtherExtents>
friend constexpr bool operator==(const extents&, const extents<OtherExtents...>&) noexcept;

private:
static constexpr size_t dynamic-index(size_t) noexcept; // exposition only
static constexpr size_t dynamic-index-inv(size_t) noexcept; // exposition only
array<size_type, rank_dynamic()> dynamic-extents{}; // exposition only
};

template <class... Integrals>
explicit extents(Integrals...)
-> extents<see below>;

template<size_t ... Extents>
constexpr size_t fwd-prod-of-extents(extents<Extents...>, size_t) noexcept; // exposition only

template<size_t ... Extents>
constexpr size_t rev-prod-of-extents(extents<Extents...>, size_t) noexcept; // exposition only
}``````

2 `extents<Extents...>` is a trivially copyable type.

3 Let Er be the rth element of Extents.

4 Er is a dynamic extent if it is equal to `dynamic_extent`, otherwise Er is a static extent.

5 Let Dr be the value of `dynamic-extents``[``dynamic-index``(`r`)]` if Er is a dynamic extent, otherwise Er.

6 The rth interval of an `extents` object is [0, Dr).

``constexpr size_t dynamic-index(size_t i) noexcept; // exposition only``

7 Precondition: `i <= rank()` is `true`

8 Returns: Number of Er with r < i for which Er is a dynamic extent.

``constexpr size_t dynamic-index-inv(size_t i) noexcept; // exposition only``

9 Precondition: `i < rank_dynamic()` is `true`

10 Returns: Minimum value of r such that `dynamic-index``(`r`+1) == i+1` is `true`.

``````template<size_t ... Extents>
constexpr size_t fwd-prod-of-extents(extents<Extents...> e, size_t i) noexcept; // exposition only``````

11 Precondition: `i < e.rank()` is `true`

12 Returns: If `i > 0` is `true` product of `e.extent(k)` for all `k` in the range [0, `i` ), otherwise `1`.

``````template<size_t ... Extents>
constexpr size_t rev-prod-of-extents(extents<Extents...>, size_t) noexcept; // exposition only``````

13 Precondition: `i < e.rank()` is `true`

14 Returns: If `i+1 < e.rank()` is `true` product of `e.extent(k)` for all `k` in the range [ `i` , `e.rank()` ), otherwise `1`.

22.7.�.3 Constructors and assignment [mdspan.extents.cons]

``````template<size_t... OtherExtents>
explicit(see below)
constexpr extents(const extents<OtherExtents...>& other) noexcept;``````

1 Constraints:

• (1.1) `sizeof...(OtherExtents) == rank()` is `true`.

• (1.2) `((OtherExtents == dynamic_extent || Extents == dynamic_extent || OtherExtents == Extents) && ...)` is `true`.

2 Preconditions: For each rank index `r` of `*this`, `static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r)` is `true`.

3 Postconditions: `*this == other` is `true`.

4 Remarks: The expression inside `explicit` is equivalent to: `(((Extents!=dynamic_extent) && (OtherExtents==dynamic_extent)) || ... )`

``````template<class... SizeTypes>
explicit constexpr extents(SizeTypes... exts) noexcept;``````

5 Constraints:

• (5.1) `(is_convertible_v<SizeTypes, size_type> && ...)` is `true`, and

• (5.2) `(is_nothrow_constructible_v<size_type, SizeTypes> && ...)` is `true`, and

• (5.3) `sizeof...(SizeTypes) == rank_dynamic() || sizeof...(SizeTypes) == rank()` is `true`. [Note: One can construct `extents` from just dynamic extents, which are all the values getting stored, or from all the extents with a precondition. — end note]

6 Let `exts_arr` be `std::array<size_type,sizeof...(SizeTypes)>{static_cast<size_type>(std::move(exts))...}`

7 Preconditions: If `sizeof...(SizeTypes) != rank_dynamic()` is `true`, `exts_arr[r]` equals Er for each r for which Er is a static extent.

8 Postconditions: `*this == extents(exts_arr)` is `true`.

``````template<class SizeType, size_t N>
explicit(N != rank_dynamic())
constexpr extents(const array<SizeType, N>& exts) noexcept;``````

9 Constraints:

• (9.1) `is_convertible_v<const SizeType&, size_type>` equals `true`, and

• (9.2) `is_nothrow_constructible_v<size_type, const SizeType&>` is `true`, and

• (9.3) `N` equals `rank_dynamic()` or `rank()`.

10 Preconditions: If `N + rank_dynamic()` is `true`, `exts[r]` equals Er for each r for which Er is a static extent.

11 Effects:

• (11.1) If `N` equals `dynamic_rank()`, direct-non-list-initializes `dynamic-extent` with `exts`.

• (11.2) Otherwise, for all d in the range [0, `rank_dynamic()`), direct-non-list-initializes `dynamic-extent``[d]` with `exts[``dynamic-index-inv``(d)]`.

``````template <class... Integrals>
explicit extents(Integrals...) -> extents<see below>;``````

12 Constraints: `(is_convertible_v<Integrals, size_t> && ...)` is `true`.

13 Remarks: The deduced type is `dextents<sizeof...(Integrals)>`.

22.7.�.3 Observers of the domain multidimensional index space [mdspan.extents.obs]

``constexpr size_type static_extent(size_t i) const noexcept;``

1 Preconditions: `i < rank()` is `true`.

2 Returns: Ei.

``constexpr size_type extent(size_t i) const noexcept;``

3 Preconditions: `i < rank()` is `true`.

4 Returns: Di.

22.7.�.4 `extents` comparison operators [mdspan.extents.compare]

``````template<size_t... OtherExtents>
friend constexpr bool operator==(const extents& lhs,
const extents<OtherExtents...>& rhs) noexcept;``````

1 Returns: true if `lhs.rank()` equals `rhs.rank()` and `lhs.extents(r)` equals `rhs.extents(r)` for every rank index `r` of `rhs`, otherwise `false`.

22.7.�.5 Template alias `dextents` [mdspan.extents.dextents]

``````template <size_t Rank>
using dextents = see below;``````

1 Result: A type `E` that is a specialization of `extents` such that `E::rank() == Rank && E::rank() == E::rank_dynamic()` is `true`.

22.7.� Layout mapping policy [mdspan.layout]

1 In subclause 22.7.�.1 and subclause 22.7.�.2

• (1.1) `M` denotes a layout mapping class.

• (1.2) `MP` denotes a layout mapping policy class.

• (1.3) `m` denotes a (possibly const) value of type `M`.

• (1.4) `E` denotes a specialization of `extents`.

• (1.5) `e` denotes a (possibly const) value of type `E`.

• (1.6) `i...` and `j...` are multidimensional indices into `e` ([mdspan.terms]).

• (1.7) `r` is a (possibly const) rank index.

• (1.8) `dr...` is a (possibly const) pack of integers for which `sizeof...(dr) == e.rank()` is `true`, the `r`-th element is equal to `1`, and all other elements are equal to `0`.

22.7.�.2 Layout mapping requirements [mdspan.layout.reqmts]

1 A type `M` meets the layout mapping requirements if `M` meets the requirements of Cpp17CopyConstructible, Cpp17CopyAssignable, and Cpp17EqualityComparable, and the following types and expressions are well-formed and have the specified semantics.

``is_nothrow_move_constructible_v<M>``

2 Value: `true`.

``is_nothrow_move_assignable_v<M>``

3 Value: `true`.

``typename M::extents_type``

4 Result: `E`.

``typename M::size_type``

5 Result: `typename M::extents_type::size_type`.

``typename M::layout_type``

6 Result: `MP`

7 Preconditions: `is_same_v<typename MP::template mapping<typename M::extents_type>, M>` is `true` and `MP` meets the layout mapping policy requirements ([mdspan.layoutpolicy.reqmts]).

``m.extents()``

8 Result: `const typename M::extents_type&`

9 Value: `e`

``m(i...)``

10 Result: `typename M::size_type`

11 Postcondition: `m(i...)==m(static_cast<typename M::size_type>(i)...)` is `true`.

``m.required_span_size()``

12 Result: `typename M::size_type`

13 Value: If the multidimensional index space that `e` defines is empty, then `0`, else `1` plus the maximum value of `m(i...)` for all `i...` in `e`.

``m.is_unique()``

14 Result: `bool`

15 Value: `true` if for every `i...` and `j...` where `(i != j || ...)` is `true`, `m(i...) != m(j...)` is `true`. Otherwise `false`.

``m.is_contiguous()``

16 Result: `bool`

17 Value: `true` if for all k in the range [0, `m.required_span_size()` ) there exists an `i...` such that `m(i...)` equals k. Otherwise `false`.

``m.is_strided()``

18 Result: `bool`

19 Value: `true` if for every `r` there exists an integer `sr` such that, for all `i...` where `(i+dr)...` is a multidimensional index into `e` ([mdspan.terms]), `m((i+dr)...) - m(i...)` equals `sr`. Otherwise, `false`.

``M::is_always_unique()``

20 Result: `bool`

21 Value: `true` if `m.is_unique()` is `true` for all objects `m` of type `M`. Otherwise, `false`.

22 Remarks: `M::is_always_unique()` is a core constant expression ([expr.const]).

``M::is_always_contiguous()``

23 Result: `bool`

24 Value: `true` if `m.is_contiguous()` is `true` for all objects `m` of type `M`. Otherwise, `false`.

25 Remarks: `M::is_always_contiguous()` is a core constant expression ([expr.const]).

``M::is_always_strided()``

26 Result: `bool`

27 Value: `true` if `m.is_strided()` is `true` for all objects `m` of type `M`. Otherwise, `false`.

28 Remarks: `M::is_always_strided()` is a core constant expression ([expr.const]).

``m.stride(r)``

29 Preconditions: `m.is_strided()` is `true`.

30 Result: `typename M::size_type`

31 Value: `sr` as defined in `m.is_strided()` above.

22.7.�.3 Layout mapping policy requirements [mdspan.layoutpolicy.reqmts]

1 A type `MP` meets the layout mapping policy requirements if the following types are well formed and have the specified semantics.

``typename MP::template mapping_type<E>``

2 Result: `M`

3 Preconditions: `M` meets the layout mapping requirements ([mdspan.layout.reqmts]), `is_same_v<typename M::layout_type,MP>` is `true` and `is_same<typename M::extents_type,E>` is `true`

22.7.�.3 Class template `layout_left` [mdspan.layout.left]

1 `layout_left` provides a layout mapping where the left-most extent is stride one and strides increase left-to-right as the product of extents.

``````namespace std {

struct layout_left {
template<class Extents>
class mapping {
public:
using extents_type = Extents;
using size_type = typename extents_type::size_type;
using layout_type = layout_left;

constexpr mapping() noexcept = default;
constexpr mapping(const mapping&) noexcept = default;
constexpr mapping(const extents_type&) noexcept;
template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,extents_type>)
constexpr mapping(const mapping<OtherExtents>&) noexcept;
template<class LayoutRightMapping>
explicit(see below)
constexpr mapping(const LayoutRightMapping&) noexcept;
template<class LayoutStrideMapping>
explicit(extents_type::rank() > 0) constexpr mapping(
const LayoutStrideMapping& other);

constexpr mapping& operator=(const mapping&) noexcept = default;

constexpr const extents_type& extents() const noexcept { return extents_; }

constexpr size_type required_span_size() const noexcept;

template<class... Indices>
constexpr size_type operator()(Indices...) const noexcept;

static constexpr bool is_always_unique() noexcept { return true; }
static constexpr bool is_always_contiguous() noexcept { return true; }
static constexpr bool is_always_strided() noexcept { return true; }

constexpr bool is_unique() const noexcept { return true; }
constexpr bool is_contiguous() const noexcept { return true; }
constexpr bool is_strided() const noexcept { return true; }

constexpr size_type stride(size_t) const noexcept;

template<class OtherExtents>
friend constexpr bool operator==(const mapping&, const mapping<OtherExtents>&) noexcept;

private:
extents_type extents_{}; // exposition only
};
};
}``````

2 `layout_left` meets the requirements of layout mapping policy.

3 `layout_left` is a trivially copyable type. `layout_left::mapping<E>` is a trivially copyable type for each `E` that is a specialization of `extents`.

4 If `Extents` is not a specialization of `extents`, then the program is ill-formed.

22.7.�.3.1 `layout_left::mapping` members [mdspan.layout.layout_left]

``constexpr mapping(const extents_type& e) noexcept;``

1 Effects: Direct-non-list-initializes extents_ with `e`.

``````template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,extents_type>)
constexpr mapping(const mapping<OtherExtents>& other) noexcept;``````

2 Constraints: `is_constructible_v<extents_type,OtherExtents>` is `true`.

3 Effects: Direct-non-list-initializes extents_ with `other.extents()`.

``````template<class LayoutRightMapping>
explicit(!std::is_convertible_v<typename LayoutRightMapping::extents_type,extents_type>)
constexpr mapping(const LayoutRightMapping& other) noexcept;``````

4 Constraints:

• (4.1) `is_same_v<typename layout_right::template mapping<typename LayoutRightMapping::extents_type>,LayoutRightMapping>` is `true`.

• (4.2) `extents_type::rank() <= 1` is `true`, and

• (4.3) `is_constructible_v<extents_type,typename LayoutRightMapping::extents_type>` is `true`.

5 Effects: Direct-non-list-initializes extents_ with `other.extents()`.

``````template<class LayoutStrideMapping>
explicit(extents_type::rank() > 0) constexpr mapping(
const LayoutStrideMapping& other);``````

6 Constraints:

• (6.1) `is_same_v<typename layout_stride::template mapping<typename LayoutStrideMapping::extents_type>,LayoutStrideMapping>` is `true`.

• (6.2) `is_constructible_v<extents_type,typename LayoutRightMapping::extents_type>` is `true`.

7 Preconditions:

• (7.1) if `extents_type::rank() > 0` then for all `r` in the range [0, `extents_type::rank()`), `other.stride(r)` equals `fwd-prod-of-extents``(extents(),r)`.

8 Effects: Direct-non-list-initializes extents_ with `other.extents()`.

``constexpr size_type required_span_size() const noexcept;``

9 Returns: `fwd-prod-of-extents``(extents(),extents_type::rank())`.

``````template<class... Indices>
constexpr size_type operator()(Indices... i) const noexcept;``````

10 Constraints:

• (10.1) `sizeof...(Indices) == extents_type::rank()` is `true`,

• (10.2) `(is_convertible_v<Indices, size_type> && ...)` is `true`, and

• (10.3) `(is_nothrow_constructible_v<size_type, Indices> && ...)` is `true`.

11 Preconditions: `static_cast<size_type>(i)...` is a multidimensional index into extents_ ([mdspan.terms]).

12 Effects: Let `P...` be the parameter pack such that `is_same_v<make_index_sequence<sizeof...(Indices)>, index_sequence<P...>>` is `true`.
Equivalent to: `return ((static_cast<size_type>(i)*stride(P)) + ... + 0);`

``constexpr size_type stride(size_t i) const;``

13 Constraints: `extents_type::rank() > 0` is `true`.

14 Preconditions: `i < extents_type::rank()` is `true`.

15 Returns: `fwd-prod-of-extents``(extents(), i)`.

``````template<class OtherExtents>
friend constexpr bool operator==(const mapping& x, const mapping<OtherExtents>& y) noexcept;``````

16 Constraints: `extents_type::rank() == OtherExtents::rank()` is `true`.

17 Effects: Equivalent to: `return x.extents() == y.extents();`

22.7.�.4 Class template `layout_right` [mdspan.layout.right]

1 `layout_right` provides a layout mapping where the right-most extent is stride one and strides increase right-to-left as the product of extents.

``````namespace std {

struct layout_right {
template<class Extents>
class mapping {
public:
using extents_type = Extents;
using size_type = typename extents_type::size_type;
using layout_type = layout_right;

constexpr mapping() noexcept = default;
constexpr mapping(const mapping&) noexcept = default;
constexpr mapping(const extents_type&) noexcept;
template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,extents_type>)
constexpr mapping(const mapping<OtherExtents>&) noexcept;
template<class LayoutLeftMapping>
explicit(see below)
constexpr mapping(const LayoutLeftMapping&) noexcept;
template<class LayoutStrideMapping>
explicit(extents_type::rank() > 0) constexpr mapping(
const LayoutStrideMappping& other) noexcept;

constexpr mapping& operator=(const mapping&) noexcept = default;

constexpr const extents_type& extents() const noexcept { return extents_; }

constexpr size_type required_span_size() const noexcept;

template<class... Indices>
constexpr size_type operator()(Indices...) const noexcept;

static constexpr bool is_always_unique() noexcept { return true; }
static constexpr bool is_always_contiguous() noexcept { return true; }
static constexpr bool is_always_strided() noexcept { return true; }

constexpr bool is_unique() const noexcept { return true; }
constexpr bool is_contiguous() const noexcept { return true; }
constexpr bool is_strided() const noexcept { return true; }

constexpr size_type stride(size_t) const noexcept;

template<class OtherExtents>
friend constexpr bool operator==(const mapping&, const mapping<OtherExtents>&) noexcept;

private:
extents_type extents_{}; // exposition only
};
};
}``````

2 `layout_right` meets the requirements of layout mapping policy.

3 `layout_right` is a trivially copyable type. `layout_right::mapping<E>` is a trivially copyable type for each `E` that is a specialization of `extents`.

4 If `Extents` is not a specialization of `extents`, then the program is ill-formed.

22.7.�.4.1 `layout_right::mapping` members [mdspan.layout.layout_right]

``constexpr mapping(const extents_type& e) noexcept;``

1 Effects: Direct-non-list-initializes extents_ with `e`.

``````template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,extents_type>)
constexpr mapping(const mapping<OtherExtents>& other) noexcept;``````

2 Constraints: `is_constructible_v<extents_type,OtherExtents>` is `true`.

3 Effects: Direct-non-list-initializes extents_ with `other.extents()`.

``````template<class LayoutLeftMapping>
explicit(!std::is_convertible_v<typename LayoutLeftMapping::extents_type,extents_type>)
constexpr mapping(const LayoutLeftMapping& other) noexcept;``````

4 Constraints:

• (4.1) `is_same_v<typename layout_left::template mapping<typename LayoutLeftMapping::extents_type>,LayoutLeftMapping>` is `true`.

• (4.2) `extents_type::rank() <= 1` is `true`, and

• (4.3) `is_constructible_v<extents_type,typename LayoutLeftMapping::extents_type>` is `true`.

5 Effects: Direct-non-list-initializes extents_ with `other.extents()`.

``````template<class LayoutStrideMapping>
explicit(extents_type::rank() > 0) constexpr mapping(
const LayoutStrideMapping& other) noexcept;``````

6 Constraints:

• (6.1) `is_same_v<typename layout_stride::template mapping<typename LayoutStrideMapping::extents_type>,LayoutStrideMapping>` is `true`.

• (6.2) `is_constructible_v<extents_type,typename LayoutStrideMapping::extents_type>` is `true`.

7 Preconditions:

• (7.1) if `extents_type::rank() > 0` then for all `r` in the range [0, `extents_type::rank()`), `other.stride(r)` equals `rev-prod-of-extents``(extents(),r+1)`.

8 Effects: Direct-non-list-initializes extents_ with `other.extents()`.

``size_type required_span_size() const noexcept;``

9 Returns: `fwd-prod-of-extents``(extents(), extents_type::rank())`

``````template<class... Indices>
constexpr size_type operator()(Indices... i) const noexcept;``````

10 Constraints:

• (10.1) `sizeof...(Indices) == extents_type::rank()` is `true`, and

• (10.2) `(is_convertible_v<Indices, size_type> && ...)` is `true`.

• (10.3) `(is_nothrow_constructlible_v<size_type, Indices> && ...)` is `true`.

11 Preconditions: `static_cast<size_type>(i)...` is a multidimensional index into extents_ ([mdspan.terms]).

12 Effects: Let `P...` be the parameter pack such that `is_same_v<make_index_sequence<sizeof...(Indices)>, index_sequence<P...>>` is `true`.
Equivalent to: `return ((static_cast<size_type>(i)*stride(P)) + ... + 0);`

``constexpr size_type stride(size_t i) const noexcept;``

13 Constraints: `extents_type::rank() > 0` is `true`.

14 Preconditions: `i < extents_type::rank()` is `true`.

15 Returns: `rev-prod-of-extents``(extents(), i+1)`

``````template<class OtherExtents>
friend constexpr bool operator==(const mapping& x, const mapping<OtherExtents>& y) noexcept;``````

16 Constraints: `extents_type::rank() == OtherExtents::rank()` is `true`.

17 Effects: Equivalent to: `return x.extents() == y.extents();`.

22.7.�.5 Class template `layout_stride` [mdspan.layout.stride]

1 `layout_stride` provides a layout mapping where the strides are user defined.

``````namespace std {

struct layout_stride {
template<class Extents>
class mapping {
public:
using extents_type = Extents;
using size_type = typename extents_type::size_type;
using layout_type = layout_stride;

constexpr mapping() noexcept = default;
constexpr mapping(const mapping&) noexcept = default;
template<class SizeType, size_t N>
constexpr mapping(const extents_type&,
const array<SizeType, N>&) noexcept;

template<class StridedLayoutMapping>
explicit(see below)
constexpr mapping(const StridedLayoutMapping&) noexcept;

constexpr mapping& operator=(const mapping&) noexcept = default;

constexpr const extents_type& extents() const noexcept { return extents_; }
constexpr array<typename size_type, extents_type::rank()> strides() const noexcept
{ return strides_; }

constexpr size_type required_span_size() const noexcept;

template<class... Indices>
constexpr size_type operator()(Indices...) const noexcept ;

static constexpr bool is_always_unique() noexcept { return true; }
static constexpr bool is_always_contiguous() noexcept { return false; }
static constexpr bool is_always_strided() noexcept { return true; }

constexpr bool is_unique() const noexcept { return true; }
constexpr bool is_contiguous() const noexcept;
constexpr bool is_strided() const noexcept { return true; }

constexpr size_type stride(size_t) const noexcept;

template<class OtherMapping>
friend constexpr bool operator==(const mapping&, const OtherMapping&) noexcept;

private:
extents_type extents_{}; // exposition only
array<size_type, extents_type::rank()> strides_{}; // exposition only
};
};
}``````

2 `layout_stride` meets the requirements of layout mapping policy.

3 `layout_stride` is a trivially copyable type. `layout_stride::mapping<E>` is a trivially copyable type for each `E` that is a specialization of `extents`.

4 If `Extents` is not a specialization of `extents`, then the program is ill-formed.

22.7.�.5.1 `layout_stride::mapping` members [mdspan.layout.layout_stride]

``````template<class SizeType, size_t N>
constexpr mapping(const extents_type& e, array<SizeType, N> s) noexcept;``````

1 Let P be a permutation of the integers 0, ..., `extents_type::rank()-1` and let pi be the ith element of P.

2 Constraints:

• (2.1) `is_convertible_v<const SizeType&, size_type>` is `true` and

• (2.2) `N == extents_type::rank()` is `true`.

3 Preconditions:

• (3.1)`s[i] > 0` is `true` for all `i` in the range [0, `extents_type::rank()` ).

• (3.2) If `extents_type::rank()` is greater than zero, then there exists a permutation P such that `s[` pi `] >= s[` pi − 1 `] * e.extent(` pi − 1 `)` is `true` for all i in the range [1, `extents_type::rank()` ). [Note: This condition guarantess that `is_unique()` is `true`. — end note]

4 Effects: Direct-non-list-initializes extents_ with `e`, and direct-non-list-initializes strides_ with `s`.

``````template<class StridedLayoutMapping>
explicit(see below)
constexpr mapping(const StridedLayoutMapping& other) noexcept;``````

5 Constraints:

• (5.1) `is_constructible_v<extents_type, StridedLayoutMapping::extents_type>` is `true`.

• (5.2) `StridedLayoutMapping::is_always_unique()` is `true`.

• (5.3) `StridedLayoutMapping::is_always_strided()` is `true`.

• (5.4) `is_same_v<typename StridedLayoutMapping::extents_type,decltype(other.extents())>` is `true`.

• (5.5) if `extents_type::rank()>0` then `is_same_v<decltype(other.stride(0)),typename StridedLayoutMapping::size_type>` is `true`.

6 Precondition: `StridedLayoutMapping` meets the layout mapping requirements.

7 Postcondition: `*this == other` is `true`.

8 Remarks: The expression inside `explicit` is equivalent to: `!std::is_convertible_v<typename StridedLayoutMapping::extents_type,extents_type>`.

``constexpr size_type required_span_size() const noexcept;``

9 Returns:

• (9.1) `1`, if `extents_type::rank()==0` is `true`, otherwise

• (9.2) `0`, if `fwd-prod-of-extents``(extents(), extents_type::rank()-1)==0` is `true`, otherwise

• (9.3) `1` plus the sum of `(extents().extent(r) - 1) * stride(r)` for all `r` in the range [0, `extents_type::rank()` ).

``````template<class... Indices>
constexpr size_type operator()(Indices... i) const noexcept;``````

10 Constraints:

• (10.1) `sizeof...(Indices) == extents_type::rank()` is `true`, and

• (10.2) `(is_convertible_v<Indices, size_type> && ...)` is `true`.

• (10.3) `(is_nothrow_constructible_v<size_type, Indices> && ...)` is `true`.

11 Preconditions: `static_cast<size_type>(i)...` is a multidimensional index into extents_ ([mdspan.terms]).

12 Effects: Let `P...` be the parameter pack such that `is_same_v<make_index_sequence<sizeof...(Indices)>, index_sequence<P...>>` is `true`.
Equivalent to: `return ((static_cast<size_type>(i)*stride(P)) + ... + 0);`

``constexpr bool is_contiguous() const noexcept;``

13 Let P be a permutation of the integers 0, ..., `extents_type::rank()-1` and let pi be the ith element of P.

14Returns:

• (14.1) `true` if `extents_type::ranks()` is zero.

• (14.2) Otherwise, `true` if there is a permutation P such that `stride(` p0 `)` equals `1`, and `stride(` pi `)` equals `stride(` pi − 1 `) * extents().extent(` pi − 1 `)` for i in the range [1, `extents_type::rank()` ).

• (14.3) Otherwise, `false`.

``````template<class OtherMapping>
friend constexpr bool operator==(const mapping& x, const OtherMapping& y) noexcept;``````

15 Constraints: `extents_type::rank() == OtherMapping::extents_type::rank()` is `true`, and `OtherMapping::is_always_strided()` is `true`.

16 Preconditions: `OtherMapping` meets layout mapping requirements.

17 Returns: If `x.extents() == y.extents()` is `true` and each of `x.stride(r) == y.stride(r)` is `true` for `r` in the range of [0, `x.extents.rank()` ), `true`. Otherwise, `false`.

22.7.� Accessor Policy [mdspan.accessor]

1 An accessor policy defines types and operations by which a set of objects are accessed from a subset of a contiguous range of integer indices.

2 In subclause 22.7.�.1,

• (2.1) `A` denotes an accessor policy.

• (2.2) `a` denotes an object of type `A`.

• (2.3) `p` denotes an object of type `A::pointer`.

• (2.4) `n`, `i` and `j` each denote a `size_t` value.

22.7.�.1 Accessor policy requirements [mdspan.accessor.reqs]

1 A type `A` meets the accessor policy requirements if `A` meets the requirements of Cpp17CopyConstructible, Cpp17CopyAssignable and the following types and expressions are well-formed and have the specified semantics.

2 [`p`,`n`) is an accessible range for `a` if for each `i` in the range [0,`n`) `a.access(p,i)` is well defined.

``is_nothrow_move_constructible_v<A>``

3 Value: `true`.

``is_nothrow_move_assignable_v<A>``

4 Value: `true`.

``typename A::element_type``
5 Result: A type that is a complete object type that is not an abstract class type.
``typename A::pointer``

6 Result: A type that meets the requirements of Cpp17DefaultConstructible, Cpp17CopyConstructible, and Cpp17CopyAssignable. `is_nothrow_move_constructible_v<A::pointer>` is `true` and `is_nothrow_move_assignable_v<A::pointer>` is `true`.

7 [Note: The type of `pointer` need not be `element_type*`. — end note]

``typename A::reference``

8 Result: A type for which `is_convertible_v<A::reference,A::element_type>` is `true`, and if `is_const_v<A::element_type>` is `false` then `is_assignable_v<A::element_type&,A::reference>` is `true`.

9 [Note: The type of `reference` need not be `element_type&`. — end note]

``typename A::offset_policy``

10 Result: An accessor policy type `O`.

11 Preconditions:

• (11.1) `O` meets the accessor policy requirements.

• (11.2) `constructible_from<O,A>` is modeled.

• (11.3) `is_same_v<typename O::element_type, typename A::element_type>` is `true`

``a.access(p, i)``

12 Result: `A::reference`

13 Value: An object which provides access to the `i`-th element in the range of elements that starts at `p`.

``a.offset(p, i)``

14 Result: `A::offset_policy::pointer`

15 Postconditions: Let `a_sub` be `A::offset_policy(a)`, `p_sub` be `a.offset(p,i)`, and `n` have a value such that [`p`,`n`) is the accessible range of `a`, then

• (15.1) [`p_sub`,`n-i`) is the accessible range of `a_sub`, and

• (15.2) `a_sub.access(p_sub,j)` provides acces to the same element as `a.access(p,i+j)`.

22.7.�.2 Class template `default_accessor` [mdspan.accessor.default]

``````namespace std {
template<class ElementType>
struct default_accessor {
using offset_policy = default_accessor;
using element_type = ElementType;
using reference = ElementType&;
using pointer = ElementType*;

constexpr default_accessor() noexcept = default;

template<class OtherElementType>
constexpr default_accessor(default_accessor<OtherElementType>) noexcept {}

constexpr typename offset_policy::pointer
offset(pointer p, size_t i) const noexcept;

constexpr reference access(pointer p, size_t i) const noexcept;
};
}``````

1 `default_accessor` meets the requirements of accessor policy.

2 `ElementType` is required to be a complete object type that is neither an abstract class type nor an array type.

3 Each specialization of `default_accessor` is a trivially copyable type.

4 For a `pointer` `p` and `size_type` `n`, [`p`,`n`) is an accessible range for an instance of `default_accessor` if and only if [`p`,`p+n`) is a valid range.

22.7.�.2 Class template `default_accessor` members [mdspan.accessor.members]

``````template<class OtherElementType>
constexpr default_accessor(default_accessor<OtherElementType>) noexcept {}``````

1 Constraints:

• (1.1) `is_convertible_v<typename default_accessor<OtherElementType>::element_type(*)[], element_type(*)[]>` is `true`.
``````constexpr typename offset_policy::pointer
offset(pointer p, size_t i) const noexcept;``````

2 Preconditions: `p + i` is dereferenceable.

3 Returns: `p + i`.

``constexpr reference access(pointer p, size_t i) const noexcept;``

4 Preconditions: `p + i` is dereferenceable.

5 Returns: `p[i]`.

22.7.� Class template `mdspan` [mdspan.mdspan]

22.7.�.1 `mdspan` overview [mdspan.mdspan.overview]

1 `mdspan` maps a multidimensional index in its domain to a reference to an element in its codomain.

2 The domain of an `mdspan` object is a multidimensional index space defined by an `extents`.

3 The codomain of an `mdspan` object is a set of elements accessible from a contiguous range of integer indices.

``````namespace std {

template<class ElementType, class Extents, class LayoutPolicy, class AccessorPolicy>
class mdspan {
public:

// Domain and codomain types
using extents_type = Extents;
using layout_type = LayoutPolicy;
using accessor_type = AccessorPolicy;
using mapping_type = typename layout_type::template mapping<extents_type>;
using element_type = ElementType;
using value_type = remove_cv_t<element_type>;
using size_type = typename Extents::size_type ;
using pointer = typename accessor_type::pointer;
using reference = typename accessor_type::reference;

// [mdspan.mdspan.cons], mdspan constructors, assignment, and destructor
constexpr mdspan() requires(rank_dynamic() != 0) = default;
constexpr mdspan(const mdspan& rhs) = default;
constexpr mdspan(mdspan&& rhs) = default;

template<class... SizeTypes>
explicit constexpr mdspan(pointer ptr, SizeTypes... exts);
template<class SizeType, size_t N>
explicit(N != rank_dynamic())
constexpr mdspan(pointer p, const array<SizeType, N>& exts);
constexpr mdspan(pointer p, const Extents& ext);
constexpr mdspan(pointer p, const mapping_type& m);
constexpr mdspan(pointer p, const mapping_type& m, const accessor_type& a);

template<class OtherElementType, class OtherExtents,
class OtherLayoutPolicy, class OtherAccessorPolicy>
explicit(see below)
constexpr mdspan(
const mdspan<OtherElementType, OtherExtents,
OtherLayoutPolicy, OtherAccessorPolicy>& other);

constexpr mdspan& operator=(const mdspan& rhs) = default;
constexpr mdspan& operator=(mdspan&& rhs) = default;

// [mdspan.mdspan.mapping], mdspan mapping domain multidimensional index to access codomain element
template<class... SizeTypes>
constexpr reference operator[](SizeTypes... indices) const;
template<class SizeType, size_t N>
constexpr reference operator[](const array<SizeType, N>& indices) const;

constexpr const accessor_type& accessor() const { return acc_; }

static constexpr size_t rank() { return Extents::rank(); }
static constexpr size_t rank_dynamic() { return Extents::rank_dynamic(); }
static constexpr size_type static_extent(size_t r) { return Extents::static_extent(r); }

constexpr const extents_type& extents() const { return map_.extents(); }
constexpr size_type extent(size_t r) const { return extents().extent(r); }
constexpr size_type size() const;

// [mdspan.basic.codomain], mdspan observers of the codomain
constexpr const pointer& data() const { return ptr_; }
constexpr const mapping_type& mapping() const { return map_; }

static constexpr bool is_always_unique() {
return mapping_type::is_always_unique();
}
static constexpr bool is_always_contiguous() {
return mapping_type::is_always_contiguous();
}
static constexpr bool is_always_strided() {
return mapping_type::is_always_strided();
}

constexpr bool is_unique() const {
return map_.is_unique();
}
constexpr bool is_contiguous() const {
return map_.is_contiguous();
}
constexpr bool is_strided() const {
return map_.is_strided();
}
constexpr size_type stride(size_t r) const {
return map_.stride(r);
}

private:
accessor_type acc_; // exposition only
mapping_type map_; // exposition only
pointer ptr_ = pointer(); // exposition only
};

template <class ElementType, class... Integrals>
explicit mdspan(ElementType*, Integrals...)
-> mdspan<see below>;

template <class ElementType, class SizeType, size_t N>
mdspan(ElementType*, const array<SizeType, N>&)
-> mdspan<see below>;

template <class ElementType, size_t... ExtentsPack>
mdspan(ElementType*, const extents<ExtentsPack...>&)
-> mdspan<see below>;

template <class ElementType, class MappingType>
mdspan(ElementType*, const MappingType&)
-> mdspan<see below>;

template <class ElementType, class MappingType, class AccessorType>
mdspan(ElementType*, const MappingType&, const AccessorType&)
-> mdspan<see below>;

}``````

5 `mdspan<ElementType, Extents, LayoutPolicy, AccessorPolicy>` is a trivially copyable type if `AccessorPolicy`, `LayoutPolicy::mapping_type<Extents>` and `AccessorPolicy::pointer` are trivially copyable types.

6 `ElementType` is required to be a complete object type that is neither an abstract class type nor an array type.

7 If `Extents` is not a specialization of `extents`, then the program is ill-formed.

8 If `LayoutPolicy` does not meet the layout mapping policy requirements, then behavior is undefined.

9 `AccessorPolicy` shall meet the accessor policy requirements. If `is_same_v<typename AccessorPolicy::element_type, ElementType>` equals `false`, then the program is ill-formed.

22.7.�.1 `mdspan` constructors and assignment operators [mdspan.mdspan.cons]

``````template<class... SizeTypes>
explicit constexpr mdspan(pointer p, SizeTypes... exts);``````

1 Constraints:

• (1.1) `(is_convertible_v<SizeTypes, size_type> && ...)` is `true`,

• (1.2) `is_constructible_v<Extents, static_cast<size_type>(SizeTypes)...>` is `true`,

• (1.3) `is_constructible_v<mapping_type, Extents>` is `true`, and

• (1.4) `is_default_constructible_v<accessor_type>` is `true`.

2 Precondition: [`p`,`map_``.required_span_size()`) is an accesible range of `acc_` for the values of `map_` and `acc_` after the invocation of this constructor.

3 Effects:

• (3.1) Direct-non-list-initializes `ptr_` with `std::move(p)`, and

• (3.2) Direct-non-list-initializes `map_` with `Extents(static_cast<size_type>(std::move(exts))...)`.

``````template<class SizeType, size_t N>
explicit(N != rank_dynamic())
constexpr mdspan(pointer p, const array<SizeType, N>& exts);``````

4 Constraints:

• (4.1) `is_convertible_v<const SizeType&, size_type>` is `true`,

• (4.2) `is_constructible_v<extents_type, const array<SizeType, N>&>` is `true`,

• (4.3) `is_constructible_v<mapping_type, extents_type>` is `true`, and

• (4.4) `is_default_constructible_v<accessor_type>` is `true`.

5 Precondition: [`p`,`map_``.required_span_size()`) is an accesible range of `acc_` for the values of `map_` and `acc_` after the invocation of this constructor.

6 Effects:

• (6.1) Direct-non-list-initializes `ptr_` with `std::move(p)`, and

• (6.2) Direct-non-list-initializes `map_` with `extents_type(exts)`.

``constexpr mdspan(pointer p, const extents_type& ext);``

7 Constraints:

• (7.1) `is_constructible_v<mapping_type, const extents_type&>` is `true`, and

• (7.2) `is_default_constructible_v<accessor_type>` is `true`.

8 Precondition: [`p`,`map_``.required_span_size()`) is an accesible range of `acc_` for the values of `map_` and `acc_` after the invocation of this constructor.

9 Effects:

• (9.1) Direct-non-list-initializes `ptr_` with `std::move(p)`, and

• (9.2) Direct-non-list-initializes `map_` with `ext`.

``constexpr mdspan(pointer p, const mapping_type& m);``

10 Constraints: `is_default_constructible_v<accessor_type>` is `true`.

11 Precondition: [`p`,`m.required_span_size()`) is an accesible range of `acc_` for value of `acc_` after the invocation of this constructor.

12 Effects:

• (12.1) Direct-non-list-initializes `ptr_` with `std::move(p)`, and

• (12.2) Direct-non-list-initializes `map_` with `m`.

``constexpr mdspan(pointer p, const mapping_type& m, const accessor_type& a);``

13 Precondition: [`p`,`m.required_span_size()`) is an accesible range of `a`.

14Effects:

• (14.1) Direct-non-list-initializes `ptr_` with `std::move(p)`,

• (14.2) Direct-non-list-initializes `map_` with `m`, and

• (14.3) Direct-non-list-initializes `acc_` with `a`.

``````template<class OtherElementType, class OtherExtents,
class OtherLayoutPolicy, class OtherAccessor>
explicit(see below)
constexpr mdspan(const mdspan<OtherElementType, OtherExtents,
OtherLayoutPolicy, OtherAccessor>& other);``````

15 Mandates:

• (15.1) `is_constructible_v<pointer, const OtherAccessor::pointer&>` is `true`;

• (15.2) `is_constructible_v<extents_type, OtherExtents>` is `true`.

16 Constraints:

• (16.1) `is_constructible_v<mapping_type, const OtherLayoutPolicy::template mapping<OtherExtents>&>` is `true`;

• (16.2) `is_constructible_v<accessor_type, const OtherAccessor&>` is `true`; and

17 Preconditions:

• (17.1) For each rank index `r` of `extents_type`, `static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r)` is `true`.

• (17.2) [`ptr_`,`map_``.required_span_size()`) is an accesible range of `acc_` for values of `ptr_`, `map_` and `acc_` after the invocation of this constructor.

18 Effects:

• (18.1) Direct-non-list-initializes `ptr_` with `other.``ptr_`,

• (18.2) Direct-non-list-initializes `map_` with `other.``map_`, and

• (18.3) Direct-non-list-initializes `acc_` with `other.``acc_`.

19 Remarks: The expression inside `explicit` is:

``````  !std::is_convertible_v<const typename OtherLayoutPolicy::mapping_type&, mapping_type> ||
!std::is_convertible_v<const OtherAccessorPolicy&, AccessorPolicy>``````
``````template <class ElementType, class... Integrals>
explicit mdspan(ElementType*, Integrals...)
-> mdspan<see below>;``````

20 Constraints: `(is_convertible_v<Integrals, size_type> && ...)` is `true`.

21 Remarks: The deduced type is `mdspan<ElementType, dextents<sizeof...(Integrals)>>`.

``````template <class ElementType, class SizeType, size_t N>
mdspan(ElementType*, const array<SizeType, N>&)
-> mdspan<see below>;``````

22 Remarks: The deduced type is `mdspan<ElementType, dextents<N>>`.

``````template <class ElementType, size_t... ExtentsPack>
mdspan(ElementType*, const extents<ExtentsPack...>&)
-> mdspan<see below>;``````

23 Remarks: The deduced type is `mdspan<ElementType, extents<ExtentsPack...>>`.

``````template <class ElementType, class MappingType>
mdspan(ElementType*, const MappingType&)
-> mdspan<see below>;``````

24 Remarks: The deduced type is `mdspan<ElementType, typename MappingType::extents_type, typename MappingType::layout_type>`.

``````template <class ElementType, class MappingType, class AccessorType>
mdspan(ElementType*, const MappingType&, const AccessorType&)
-> mdspan<see below>;``````

25 Remarks: The deduced type is `mdspan<ElementType, typename MappingType::extents_type, typename MappingType::layout_type, AccessorType>`.

22.7.�.2 `mdspan` members [mdspan.mdspan.members]

``````template<class... SizeTypes>
constexpr reference operator[](SizeTypes... indices) const;``````

1 Constraints:

• (1.1) `(is_convertible_v<SizeTypes, size_type> && ...)` is `true`,

• (1.2) `(is_nothrow_constructible_v<size_type, SizeTypes> && ...)` is `true`, and

• (1.3) `sizeof...(SizeTypes) == rank()` is `true`.

2 Let `I` be `static_cast<size_type>(std::move(indices))`.

3 Preconditions: `I...` is a multidimensional index into `extents()`. [Note: This implies that `map_``(I...) <``map_``.required_span_size()` is `true`.— end note];

4 Effects: Equivalent to: `return` `acc_``.access(``ptr_``,``map_``(I...));`.

``````template<class SizeType, size_t N>
constexpr reference operator[](const array<SizeType, N>& indices) const;``````

5 Constraints:

• (5.1) `is_convertible_v<const SizeType&, size_type>` is `true`, and

• (5.2) `is_nothrow_constructible_v<size_type, const SizeType&>` is `true`, and

• (5.3) `rank() == N` is `true`.

6 Effects: Let `P...` be the parameter pack such that `is_same_v<make_index_sequence<rank()>, index_sequence<P...>>` is `true`.
Equivalent to: `return operator[](static_cast<size_type>(indices[P])...);`

``constexpr size_type size() const;``

7 Returns: `fwd-prod-of-extents``(extents(), rank())`.

22.7.� submdspan [mdspan.submdspan]

1 `submdspan` creates an `mdspan` with a domain that is a subset of the input `mdspan`’s domain, and a codomain that is a subset of the input `mdspan`’s codomain.

2 The `SliceSpecifier` template argument(s) and the corresponding value(s) of the arguments of `submdspan` after `src` determine the subset of `src` that the `mdspan` returned by `submdspan` views.

``````namespace std {

// [mdspan.submdspan], submdspan creation
template<class ElementType, class Extents, class LayoutPolicy,
class AccessorPolicy, class... SliceSpecifiers>
constexpr auto submdspan(
const mdspan<ElementType, Extents, LayoutPolicy, AccessorPolicy>& src,
SliceSpecifiers...slices) -> see below;
}``````

3 Let `sub` be the return value of `submdspan(src, slices...)`, let sk be the k-th element of `slices...`, and let Sk be the type of the k-th element of `slices...`.

4 Let `map-rank` be an `array<size_t,Extents::rank()>` such that for all `j` in the range `[0, Extents::rank())` `map-rank``[j]` equals:

• (4.1) `dynamic_extent` if `is_convertible_v<`Sj`,size_t>` is `true`, or else

• (4.2) the number of Sk with k < j such that `is_convertible_v<`Sk`,size_t>` is `false`.

5 Let `first` and `last` be `array<size_t,Extents::rank()>`. For each rank index `r` of `src.extents()`, define the values of `first``[r]` and `last``[r]` as follows:

• (5.1) if `is_convertible_v<`Sr`,size_t>` is `true`, then `first``[r]` equals sr, and `last``[r]` equals `first``[r]` + 1;

• (5.2) otherwise, if `is_convertible_v<`Sr`,tuple<size_t,size_t>>` is `true`, then `first``[r]` equals `get<0>(t)`, and `last``[r]` equals `get<1>(t)`, where `t` is the result of converting sr to `tuple<size_t,size_t>`;

• (5.3) otherwise, if `is_convertible_v<`Sr`,full_extent_t>` is `true`, then `first``[r]` equals `0`, and `last``[r]` equals `src.extent(r)`.

6 Mandates: For each rank index `r` of `src.extents()` only one of the following is `true`: `is_convertible_v<`Sk`,size_t>`, `is_convertible_v<`Sk`,tuple<size_t,size_t>>`, `is_convertible_v<`Sk`,full_extent_t>`.

7 Constraints:

• (7.1) `sizeof(slices...)` equals `Extents::rank()`,

• (7.2) For each rank index `k` of `src.extents()`, `is_convertible_v<`Sk`,size_t> || is_convertible_v<`Sk`,tuple<size_t,size_t>> || is_convertible_v<`Sk`,full_extent_t>` is `true`.

• (7.3) `LayoutPolicy` is `layout_left`, `layout_right`, `layout_stride`, or an implementation-defined layout mapping policy type. [mdspan.layout.reqs] [Note: Implementation and user defined layout mapping policies could exist, for which taking an arbitrary `submdspan` does not make sense. — end note];

8 Preconditions: For each rank index `r` of `src.extents()`, `0 <=``first``[r] &&``first``[r] <=``last``[r] &&``last``[r] <= src.extent(r)` is `true`.

9 Effects:

• (9.1) Direct-non-list-initializes `sub.``acc_` with `src.accessor()`.

• (9.2) Direct-non-list-initializes `sub.``ptr_` with `src.accessor().offset(src.data(),apply(src.mapping(),``first``))`.

10 Postconditions:

• (10.1) For 0 ≤ `k` < `Extents::rank()`, if `map-rank``[k] != dynamic_extent` is `true`, then `sub.extent(``map-rank``[k])` equals `last``[k] -``first``[k]`.

• (10.2) Let `j...` be a multidimensional index into `sub.extents()`, let `J` be `array{static_cast<size_t>(j)...}`, let `I` be `array<size_t,decltype(src)::rank()>` such that `I[k] ==``first``[k] + (``map-rank``[k]==dynamic_extent?0:J[``map-rank``[k]])` is `true`, then `sub[J]` and `src[I]` refer to the same element.

• (10.3) If `src.is_strided()` is `true`, then `sub.is_strided()` is `true`.

11 Remarks:

• (11.1) Let `SubExtents` be a specialization of `extents` such that:

• `SubExtents::rank()` equals the number of k such that `is_convertible_v<`Sk`,tuple<size_t,size_t>> || is_convertible_v<`Sk`,full_extent_t>` is `true` and `is_convertible_v<`Sk`,size_t>` is `false`.

• For all rank index `k` of `Extents` such that `map-rank``[k] != dynamic_extent` is `true` `SubExtents::static_extent(``map-rank``[k])` equals:

• `Extents::static_extent(k)` if `is_convertible_v<`Sk`,full_extent_t>` is `true`, otherwise

• `dynamic_extent`.

• (11.2) Let `SubLayout` be a type that meets the requirements of layout mapping policy and:

• if `LayoutPolicy` is not one of `layout_left`, `layout_right`, `layout_stride`, then `SubLayout` is implementation defined, otherwise

• if `SubExtents::rank()` is `0`, then `SubLayout` is `LayoutPolicy`, otherwise

• if `LayoutPolicy` is `layout_left`, `is_convertible_v<`Sk`,full_extent_t>` is `true` for all `k` in the range [0,`SubExtents::rank()-1`), and `is_convertible_v<`Sk`,size_t>` is `false` for `k` equal `SubExtents::rank()-1`, then `SubLayout` is `layout_left`, otherwise

• if `LayoutPolicy` is `layout_right`, `is_convertible_v<`Sk`,full_extent_t>` is `true` for all `k` in the range [`Extents::rank()-SubExtents::rank()+1`,`Extents::rank()`) and `is_convertible_v<`Sk`,size_t>` is `false` for `k` equal `Extents::rank()-SubExtents::rank()`, then `SubLayout` is `layout_right`, otherwise

• `SubLayout` is `layout_stride`.

• (11.3) The return type is `mdspan<ElementType,SubExtents,SubLayout,typename Accesssor::offset_policy>` where

[Note: Example of `submdspan` use: See code below - end note]

``````// Create a mapping
using Extents3D = extents<3, dynamic_extent, 7>;
layout_right::template mapping<Extents3D> map_right(10);

// Create an mdspan viewing allocated memory
int* ptr = new int[3*8*10];
mdspan<int, Extents3D, layout_right> a(ptr, map_right);

// Initialize the span
for(int i0 = 0; i0 < a.extent(0); ++i0) {
for(int i1 = 0; i1 < a.extent(1); ++i1) {
for(int i2 = 0; i2 < a.extent(2); ++i2) {
a[i0, i1, i2] = 10000*i0 + 100*i1 + i2;
}
}
}

// Create Subspan
auto a_sub = submdspan(a, 1, tuple{4,6}, tuple{1,6});

// Print values of submdspan
for(int i0 = 0; i0 < a_sub.extent(0); ++i0) {
for(int i1 = 0; i1 < a_sub.extent(1); ++i1) {
cout << a_sub[i0, i1] << " ";
}
cout << endl;
}
delete [] ptr;

/* Output
10401 10402 10403 10404 10405
10501 10502 10503 10504 10505
*/``````

5 Implementation

There is an mdspan implementation available at https://github.com/kokkos/mdspan/.

6 Related Work

The original version of this paper, N4355, predates the “P” naming for papers.

Related papers:

• P0122 : span: bounds-safe views for sequences of objects The `mdspan` codomain concept of span is well-aligned with this paper.
• P0367 : Accessors: The P0367 Accessors proposal includes polymorphic mechanisms for accessing the memory an object or span of objects. The `AccessorPolicy` extension point in this proposal is intended to include such memory access properties.
• P0331 : Motivation and Examples for Multidimensional Array
• P0332 : Relaxed Incomplete Multidimensional Array Type Declaration
• P0454 : Wording for a Minimal `mdspan` Included proposed modification of `span` to better align `span` with `mdspan`.
• P0546 : Preparing `span` for the future Proposed modification of `span`
• P0856 : Restrict access property for `mdspan` and `span`
• P0860 : atomic access policy for `mdspan`
• P0900 : An Ontology of Properties for `mdspan`
• P2128 : Multidimensional subscript operator
• P2299 : `mdspan` and CTAD