MDSPAN
Document #: | P0009r14 |
Date: | 2021-11-14 |
Project: | Programming Language C++ LEWG |
Reply-to: |
Christian Trott <crtrott@sandia.gov> D.S. Hollman <me@dsh.fyi> Damien Lebrun-Grandie <lebrungrandt@ornl.gov> Mark Hoemmen <mhoemmen@stellarscience.com> Daniel Sunderland <dansunderland@gmail.com> H. Carter Edwards <hedwards@nvidia.com> Bryce Adelstein Lelbach <brycelelbach@gmail.com> Mauro Bianco <mbianco@cscs.ch> Ben Sander <ben.sander@amd.com> Athanasios Iliopoulos <> John Michopoulos <> Nevin Liber <nliber@anl.gov> |
tuple<size_t,size_t>
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,
< mds1_t::extents_type::static_extent<0>,
Extents::extents_type::static_extent<1>>;
mds2_tdouble* ptr = new double[mdspan1.extent(0)*mdspan2.extent(1)];
return return_t(ptr, mdspan1.extent(0),mdspan2.extent(1));
}
std::span
extents
converting constructor conditionally explicit, for cases where dynamic extents are turned into static extentsdefault_accessor
depend on convertibility of element_type(*)[]
instead of pointer
to prevent derived class to base class assignmentextents
being not implicitly convertiblemdspan
converting constructor conditionally explicit, for cases where any of the exposition only members or the template parameters are only explicitly convertibletuple
instead of pair
for subslice arguments in submdspan
.mdspan::unique_size
layout_stride
constructor to be flexible with integral types of strides arrayextents
and mdspan
constructors accept either rank_dynamic
or rank
integer arguments (or an array
of that size)shared_ptr
as pointer
layout_left
to layout_right
and vice versalayout_left
, layout_right
, and layout_stride
to each otherLEWG 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.
dextents
aliasmdspan
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.mdspan
deduction guideslayout_type
alias to layout mapping requirements and to layout_left
, layout_right
, and layout_stride
noexcept
from mdspan
required_span_size
for rank-0 mdspanlayout_stride::required_span_size
for mdspans with at least one extent being zerooperator[]
in mdspan
for multidimensional array access, and add explanation to Discussion sectionspan
in the mdspan
wording, since mdspan
does not necessarily require a backing span
(because pointer
need not be ElementType*
)layout_stride
mdspan
from pointer
and extents
static_extent
default_accessor
(when the pointers to elements are convertible) for things like default_accessor<double>
to default_accessor<const double>
ptrdiff_t
to size_t
and index_type
to size_type
, for consistency with span
and the rest of the standard library`IndexType
to SizeType
or SizeTypes
(depending if it is a single type or a parameter pack)basic_mdspan
trivially default constructiblelayout_*
types, made operator()
and stride()
constexprlayout_stride
, made assignment operators and required_span_size()
constexpr to match the other layout_*
typessubspan
to submdspan
, as this only applies to mdspan
submdspan()
constexpris_strided
all_type
to full_extent_t
and all
to full_extent
accessor_basic
to default_accessor
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
span()
from [mdspan.basic.members] description as .span()
was removed from an earlier version of this proposalmdspan_subspan
expo only type; use basic_mdspan<
see below>
insteadspan
requires reference to C++20 working draftstd::experimental::fundamentals_v3
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):
<T, N, Accessor=...> span
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).
<T, Extents, Mapper, Accessor>
basic_mdspan<T, N...> mdspan
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.
<T, Extents, Properties...> basic_mdspan
SF | F | N | A | SA |
---|---|---|---|---|
1 | 2 | 2 | 6 | 2 |
Changes from P0009r5 due to related LEWG reviews:
mdspan
to basic_mdspan
.mdspan
alias to basic_mdspan
.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:
rank()==sizeof...(indices)
.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:
mdspan
, multidimensional span, to align with span
.span
.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:
element_type
, reference
, etc).array_ref<T[N]>
in addition to array_ref<extents<N>>
.LEWG review at 2016-02-Jacksonville.
Changes from P0009r1:
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:
view
to array_ref
.view<int[][][]>::layout
should be named.is_regular
(possibly to is_affine
) to avoid overloading the term with the Regular
concept.operator()
, take integral types by value.Original non-owning multidimensional array reference (view
) paper with motivation, specification, and examples.
Related LEWG review of P0546r1 at 2017-11-Albuquerque meeting
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.
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.
A multidimensional array view views a multidimensional array, just as a span
views a one-dimensional array
or vector
.
A multidimensional array of rank maps from a tuple of indices to a single offset index. Each of the 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 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 extents.
More formally, a multidimensional array of rank maps from its domain, a multidimensional index space of rank , to its codomain, a set of objects accessible from a contiguous range of integer indices. A multidimensional index space of rank is the Cartesian product of half-open integer intervals, where the for , …, are the array’s extents. A multidimensional index is a element of a multidimensional index space.
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.
C++ currently has the following approaches that could be used to represent multidimensional arrays:
“native” arrays where all the extents are compile-time constants, like int[3][4][5]
;
pointer-of-pointers(-of-pointers…), like int***
, set up as a data structure to view multidimensional data;
arrays-of-arrays(-of-arrays…) data structures, like vector<vector<array<int, N>>>
; or
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.
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.
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 */;
::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),
std[=](int i) {
double sum = 0.0;
for(int j = 0; j < M; ++j) {
+= A[i, j] * x[j];
sum }
[i] = sum;
y});
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.
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).
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.
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.
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.
A reference implementation of this proposal under BSD license is available at: mdspan. This implementation is also available on godbolt for experimentation: godbolt.
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).
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>
.
The � character is used to denote a placeholder section number which the editor shall determine.
In [version.syn], add:
#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 = decltype(
[] <size_t... Pack> (index_sequence<Pack...>) constexpr {
return extents<
[] (auto) constexpr { return dynamic_extent; } (
<size_t, Pack>{})...>{};
integral_constant}(make_index_sequence<Rank>{}));
template<class>
constexpr size_t make_dynamic_extent() { return dynamic_extent; } // exposition only
template <class... Integrals>
explicit extents(Integrals...)
-> extents<make_dynamic_extent<Integrals>()...>;
// [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;
template <class ElementType, class... Integrals>
explicit mdspan(ElementType*, Integrals...)
-> mdspan<ElementType, dextents<sizeof...(Integrals)>;
template <class ElementType, class SizeType, size_t N>
(ElementType*, const array<SizeType, N>&)
mdspan-> mdspan<ElementType, dextents<N>>;
template <class ElementType, size_t... ExtentsPack>
(ElementType*, const extents<ExtentsPack...>&)
mdspan-> mdspan<ElementType, extents<ExtentsPack...>>;
template <class ElementType, class MappingType>
(ElementType*, const MappingType&)
mdspan-> mdspan<ElementType, typename MappingType::extents_type,
typename MappingType::layout_type>;
template <class ElementType, class MappingType, class AccessorType>
(ElementType*, const MappingType&, const AccessorType&)
mdspan-> mdspan<ElementType, typename MappingType::extents_type,
typename MappingType::layout_type, AccessorType>;
// [mdspan.submdspan]
template<class ElementType, class Extents, class LayoutPolicy,
class AccessorPolicy, class... SliceSpecifiers>
constexpr mdspan<ElementType, see below, see below,
typename AccessorPolicy::offset_policy> submdspan(
const mdspan<ElementType, Extents, LayoutPolicy, AccessorPolicy>&,
...);
SliceSpecifiers
// tag supporting submdspan
struct full_extent_t { explicit full_extent_t() = default; };
inline constexpr full_extent_t full_extent = full_extent_t{};
}
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 multidimensional index is an element within the a multidimensional index space and can be represented as a pack of integer types. The multidimensional index idx...
refers to an element within the domain of a multidimensional index space if both the following are true:
(2.1) sizeof...(idx)
is equal to rank, and
(2.2) For all i in the range [0,rank), the ith value of idx
is in the range [Ib, Ie).
3 For the following subsections, let r be a value in the range [0,rank).
22.7.� Class template extents
[mdspan.extents]
22.7.�.1 Overview [mdspan.extents.syn]
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;
constexpr extents(extents&&) noexcept = default;
constexpr extents& operator=(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) + ...); }
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 i) noexcept; // exposition only
<size_type, rank_dynamic()> dynamic_extents_{}; // exposition only
array};
}
22.7.�.2 Overview [mdspan.extents.overview]
1 The class template extents
represents a multidimensional index space of rank equal to sizeof...(Extents)
.
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. For each Er that is a dynamic extent, the upper bound of the interval is stored in the exposition-only array dynamic_extents_
at dynamic_extents_[dynamic_index(
r)]
.
5 If Er is a dynamic extent, let Dr be the value of dynamic_extents_[dynamic_index(
r)]
. The rth interval of an extents
object is as follows:
constexpr size_t dynamic_index(size_t i) noexcept; // exposition only
i <= sizeof...(Extents)
is true
, the number of arguments before the i
th template parameter in the template parameter pack Extents
equivalent to dynamic_extent
. Otherwise, rank_dynamic()
.constexpr size_t dynamic_index_inv(size_t i) noexcept; // exposition only
i < rank_dynamic()
is true, the minimum value of r such that dynamic_index(r+1)
equals i + 1. Otherwise returns rank()
.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:
2 Preconditions: For all r
, static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r)
is true
.
3 Effects: For each r
where static_extent(r) == dynamic_extent
is true
, assigns other.extent(r)
to dynamic_extent[dynamic_index(r)]
.
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:
6 Preconditions: If the following three conditions are true, sizeof...(SizeTypes) == rank()
is true
, r is smaller than sizeof...(SizeTypes)
, and Er is a static extent; then Er equals std::array<size_type,rank()>{exts...}[r]
.
7 Effects:
template<class SizeType, size_t N>
explicit(N != rank_dynamic())
constexpr extents(const array<SizeType, N>& exts) noexcept;
8 Constraints:
9 Preconditions: If N
equals rank()
, r is smaller than N
and Er is a static extent, then Er equals exts[r]
.
10 Effects:
22.7.�.3 Observers of the domain multidimensional index space [mdspan.extents.obs]
constexpr size_type static_extent(size_t r) const noexcept;
constexpr size_type extent(size_t r) const noexcept;
3 Preconditions: r < rank()
is true
.
4 Returns: dynamic_extents_[dynamic_index(r)]
if static_extent(r) == dynamic_extent
is true
, otherwise static_extent(r)
.
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;
lhs.rank()
equals rhs.rank()
and lhs.extents(r)
equals rhs.extents(r)
for all r
, otherwise false
.
22.7.� Layout mapping policy [mdspan.layout]
22.7.�.1 Layout mapping requirements [mdspan.layout.reqs]
A layout mapping policy is a class that contains a layout mapping, which is a nested class template.
A layout mapping policy and its layout mapping nested class template meet the requirements in Table �.
A layout mapping meets the requirements of Cpp17CopyConstructible, Cpp17CopyAssignable, and Cpp17EqualityComparable.
In Table �:
MP
denotes a layout mapping policy.M
denotes a specialization of the layout mapping policy’s nested layout mapping template class.E
denotes a specialization of extents
.e
denotes an object of type E
.m
denotes an object of type M
.i...
and j...
are multidimensional indices in the multidimensional index space defined by e
.r
is an integral value in the range [0, e.rank())
.dr...
is an integer pack where sizeof...(dr)
equals e.rank()
, the r
-th element is equal to 1
, and all other elements are 0
.Expression | Return Type | Returns | Preconditions |
---|---|---|---|
MP::template mapping<E>
|
M
|
||
M::extents_type
|
E
|
||
M::layout_type
|
MP
|
||
is_nothrow_move_constructible_v<M>
|
bool
|
true
|
|
is_nothrow_move_assignable_v<M>
|
bool
|
true
|
|
m.extents()
|
E
|
Returns: e .
|
|
m(i...)
|
E::size_type
|
Returns: A value in the range of [0, required_span_size() ) defined by applying the layout mapping to a multidimensional index i... .
|
Preconditions: 0 ≤ array{i...}[r] < extents().extent(r) for all r in the range [0, Extents::rank() ).
|
m.required_span_size()
|
E::size_type
|
Returns: If the multidimensional index space that e defines is empty, then zero, else 1 plus the maximum value of m(i...) for all i... in e .
|
|
m.is_unique()
|
bool
|
Returns: true if for every i... and j... where (i != j || ...) is true , m(i...) != m(j...) is true .
|
|
m.is_contiguous()
|
bool
|
Returns: 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()
|
bool
|
Returns: true if for every r there exists an integer sr such that, for all j... and i... in e , where j... equals (i+dr)... , m(j...) - m(i...) equals sr . Otherwise, false .
|
|
M::is_always_unique()
|
bool
|
Returns: true if m.is_unique() is true for all objects of type M .
|
|
M::is_always_contiguous()
|
bool
|
Returns: true if m.is_contiguous() is true for all objects of type M .
|
|
M::is_always_strided()
|
bool
|
Returns: true if m.is_strided() is true for all objects of type M .
|
|
m.stride(r)
|
E::size_type
|
Returns: sr as defined in m.is_strided() above.
|
Preconditions: m.is_strided() is true .
|
22.7.�.2 Class template layout_left
[mdspan.layout.left]
1 layout_left
meets the requirements of layout mapping policy.
2 layout_left
is a trivially copyable type. layout_left::mapping<Extents>
is a trivially copyable type.
3 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.
4 If Extents
is not a (possibly cv-qualified) specialization of extents
, then the program is ill-formed.
namespace std {
struct layout_left {
template<class Extents>
class mapping {
public:
using size_type = typename Extents::size_type;
using extents_type = Extents;
using layout_type = layout_left;
constexpr mapping() noexcept = default;
constexpr mapping(const mapping&) noexcept = default;
constexpr mapping(mapping&&) noexcept = default;
constexpr mapping(const Extents&) noexcept;
template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,Extents>)
constexpr mapping(const mapping<OtherExtents>&) noexcept;
template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,Extents>)
constexpr mapping(const layout_right::template mapping<OtherExtents>&) noexcept
requires(rank() <= 1);
template<class OtherExtents>
explicit(rank() > 0) constexpr mapping(
const layout_stride::template mapping<OtherExtents>& other);
constexpr mapping& operator=(const mapping&) noexcept = default;
constexpr mapping& operator=(mapping&&) noexcept = default;
constexpr Extents 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:
{}; // exposition only
Extents extents_};
};
}
22.7.�.2.1 layout_left::mapping
members [mdspan.layout.layout_left]
constexpr mapping(const Extents& e) noexcept;
extents_
with e
.template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,Extents>)
constexpr mapping(const mapping<OtherExtents>& other) noexcept;
2 Constraints: is_constructible_v<Extents,OtherExtents>
is true
.
3 Effects: Initializes extents_
with other.extents()
.
template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,Extents>)
constexpr mapping(const layout_right::template mapping<OtherExtents>& other) noexcept
requires(rank() <= 1);
4 Constraints: is_constructible_v<Extents,OtherExtents>
is true
.
5 Effects: Initializes extents_
with other.extents()
.
template<class OtherExtents>
explicit(rank() > 0) constexpr mapping(
const layout_stride::template mapping<OtherExtents>& other);
6 Constraints: is_constructible_v<Extents,OtherExtents>
is true
.
7 Preconditions:
8 Effects: Initializes extents_
with other.extents()
.
constexpr size_type required_span_size() const noexcept;
Extents::rank() > 0
is true, the product of extents().extent(r)
for all r
in the range [0, Extents::rank()
). Otherwise, 1.template<class... Indices>
constexpr size_type operator()(Indices... i) const noexcept;
10 Constraints:
11 Preconditions: 0 ≤ array{i...}[r]
< extents().extent(r)
for all r
in the range [0, Extents::rank()
).
12 Effects: Let P...
be the parameter pack such that is_same_v<make_index_sequence<size_type, sizeof...(Indices)>, integer_sequence<size_type, P...>>
is true
.
Equivalent to: return Extents::rank() > 0 ? (i*stride(P()) + ...) : 0;
constexpr size_type stride(size_t r) const;
1
if r
equals zero, otherwise, the product of extents().extent(k)
for all k
in the range [0, r
).template<class OtherExtents>
friend constexpr bool operator==(const mapping& x, const mapping<OtherExtents>& y) noexcept;
return x.extents() == y.extents();
.
22.7.�.3 Class template layout_right
[mdspan.layout.right]
1 layout_right
meets the requirements of layout mapping policy.
2 layout_right
is a trivially copyable type. layout_right::mapping<Extents>
is a trivially copyable type.
3 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.
4 If Extents
is not a (possibly cv-qualified) specialization of extents
, then the program is ill-formed.
namespace std {
struct layout_right {
template<class Extents>
class mapping {
public:
using size_type = typename Extents::size_type;
using extents_type = Extents;
using layout_type = layout_right;
constexpr mapping() noexcept = default;
constexpr mapping(const mapping&) noexcept = default;
constexpr mapping(mapping&&) noexcept = default;
constexpr mapping(const Extents&) noexcept;
template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,Extents>)
constexpr mapping(const mapping<OtherExtents>&) noexcept;
template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,Extents>)
constexpr mapping(const layout_left::template mapping<OtherExtents>&) noexcept
requires(rank() <= 1);
template<class OtherExtents>
explicit(rank() > 0) constexpr mapping(
const layout_stride::template mapping<OtherExtents>& other) noexcept;
constexpr mapping& operator=(const mapping&) noexcept = default;
constexpr mapping& operator=(mapping&&) noexcept = default;
constexpr Extents 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:
{}; // exposition only
Extents extents_};
};
}
22.7.�.3.1 layout_right::mapping
members [mdspan.layout.layout_right]
constexpr mapping(const Extents& e) noexcept;
extents_
with e
.template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,Extents>)
constexpr mapping(const mapping<OtherExtents>& other) noexcept;
2 Constraints: is_constructible_v<Extents,OtherExtents>
is true
.
3 Effects: Initializes extents_
with other.extents()
.
template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,Extents>)
constexpr mapping(const layout_left::template mapping<OtherExtents>& other) noexcept
requires(rank() <= 1);
4 Constraints: is_constructible_v<Extents,OtherExtents>
is true
.
5 Effects: Initializes extents_
with other.extents()
.
template<class OtherExtents>
explicit(rank() > 0) constexpr mapping(
const layout_stride::template mapping<OtherExtents>& other) noexcept;
6 Constraints: is_constructible_v<Extents,OtherExtents>
is true
.
7 Preconditions:
8 Effects: Initializes extents_
with other.extents()
.
() const noexcept; size_type required_span_size
Extents::rank() > 0
is true, the product of extents().extent(r)
for all r
in the range [0, Extents::rank()
). Otherwise, 1.template<class... Indices>
constexpr size_type operator()(Indices... i) const noexcept;
10 Constraints:
11 Preconditions: 0 ≤ array{i...}[r]
< extents().extent(r)
for all r
in the range [0, Extents::rank()
).
11 Effects: Let P...
be the parameter pack such that is_same_v<make_index_sequence<size_type, sizeof...(Indices)>, integer_sequence<size_type, P...>>
is true
.
Equivalent to: return Extents::rank() > 0 ? (i*stride(P()) + ...) : 0;
constexpr size_type stride(size_t r) const noexcept;
1
if r
equals Extents::rank()-1
, otherwise, the product of extents().extent(k)
for all k
in the range [ r+1
, Extents::rank()
).template<class OtherExtents>
friend constexpr bool operator==(const mapping& x, const mapping<OtherExtents>& y) noexcept;
return x.extents() == y.extents();
.
22.7.�.4 Class template layout_stride
[mdspan.layout.stride]
1 layout_stride
meets the requirements of layout mapping policy.
2 layout_stride
is a trivially copyable type. layout_stride::mapping<Extents>
is a trivially copyable type.
3 The layout mapping property layout_stride
gives a layout mapping where the strides are user defined.
4 If Extents
is not a (possibly cv-qualified) specialization of extents
, then the program is ill-formed.
namespace std {
struct layout_stride {
template<class Extents>
class mapping {
public:
using size_type = typename Extents::size_type;
using extents_type = Extents;
using layout_type = layout_stride;
constexpr mapping() noexcept = default;
constexpr mapping(const mapping&) noexcept = default;
constexpr mapping(mapping&&) noexcept = default;
template<class SizeType, size_t N>
constexpr mapping(const Extents&,
const array<SizeType, N>&) noexcept;
template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,Extents>)
constexpr mapping(const mapping<OtherExtents>&) noexcept;
template<class LayoutMapping>
explicit(see below)
constexpr mapping(const LayoutMapping&) noexcept;
constexpr mapping& operator=(const mapping&) noexcept = default;
constexpr mapping& operator=(mapping&&) noexcept = default;
constexpr Extents extents() const noexcept { return extents_; }
constexpr array<typename size_type, Extents::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 OtherExtents>
friend constexpr bool operator==(const mapping&, const mapping<OtherExtents>&) noexcept;
private:
{}; // exposition only
Extents extents_<size_type, Extents::rank()> strides_{}; // exposition only
array};
};
}
22.7.�.4.1 layout_stride::mapping
members [mdspan.layout.layout_stride]
template<class SizeType, size_t N>
constexpr mapping(const Extents& e, array<SizeType, N> s) noexcept;
1 Let P be a permutation of the integers 0, ..., Extents::rank()-1
and let pi be the ith element of P.
2 Constraints:
3 Preconditions:
4 Effects: Initializes extents_
with e
, and initializes strides_
with s
.
template<class OtherExtents>
explicit(!std::is_convertible_v<OtherExtents,Extents>)
constexpr mapping(const mapping<OtherExtents>& other) noexcept;
5 Constraints: is_constructible_v<Extents,OtherExtents>
is true
.
6 Effects: Initializes extents_
with other.extents()
, and initializes strides_
with other.strides()
.
template<class LayoutMapping>
explicit(see below)
constexpr mapping(const LayoutMapping& other) noexcept;
7 Constraints:
(7.1) LayoutMapping::layout_type
meets the layout mapping policy requirements.
(7.2) std::is_same_v<LayoutMapping, LayoutMapping::layout_type::mapping<LayoutMapping::extents_type>
is true
.
(7.3) is_constructible_v<Extents, LayoutMapping::extents_type>
is true
.
(7.4) other.is_always_unique()
is true
.
(7.5) other.is_always_strided()
is true
.
8 Effects: Initializes extents_
with other.extents()
, and initializes strides_
such that strides_[r] == other.stride(r)
is true for all r in the range [0, Extents::rank()
).
9 Remarks: The expression inside explicit
is equivalent to: !std::is_convertible_v<typename LayoutMapping::extents_type,Extents>
.
constexpr size_type required_span_size() const noexcept;
(Extents::rank() > 0)
is true and (extents().extent(r) > 0)
is true for all r
in the range [0, Extents::rank()
), the maximum of extents().extent(r) * stride(r)
for all r
in the range [0, Extents::rank()
). Otherwise, Extents::rank() == 0 ? 1 : 0
.template<class... Indices>
constexpr size_type operator()(Indices... i) const noexcept;
12 Constraints:
13 Preconditions: 0 ≤ array{i...}[r]
< extents().extent(r)
for all r
in the range [0, Extents::rank()
).
14 Effects: Let P...
be the parameter pack such that is_same_v<make_index_sequence<size_type, sizeof...(Indices)>, integer_sequence<size_type, P...>>
is true
.
Equivalent to: return Extents::rank() > 0 ? (i*stride(P()) + ...) : 0;
constexpr bool is_contiguous() const noexcept;
15 Let P be a permutation of the integers 0, ..., Extents::rank()-1
and let pi be the ith element of P.
16Returns:
template<class OtherExtents>
friend constexpr bool operator==(const mapping& x, const mapping<OtherExtents>& y) noexcept;
return x.extents() == y.extents();
.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.
22.7.�.1 Accessor policy requirements [mdspan.accessor.reqs]
2 An accessor policy defines:
(2.1) pointer
, the type of a handle to a set of objects accessible from a subset of a contiguous range of integer indices;
(2.2) reference
, the type of a handle to a single element of type element_type
;
(2.3) access
, a method to access the i
-th element in the range of elements represented by a pointer
;
(2.4) offset
, a method to create a subrange, beginning at an integer offset value and accessible from a subset of a contiguous range of integer indices, of a given range of elements; and
(2.5) offset_policy
, the type of a [Note: possibly different — end note] accessor policy for accessing the result of offset
.
3 [Note: The type of reference
need not be element_type&
. The type of pointer
need not be element_type*
. — end note]
5 In Table �:
(5.1) A
denotes an accessor policy.
(5.2) a
denotes an object of type A
.
(5.3) p
denotes an object of type A::pointer
.
(5.4) i
and j
each denote a size_t
value.
Expression | Return Type | Requirements |
---|---|---|
A
|
A meets the requirements of Cpp17CopyConstructible and Cpp17CopyAssignable. is_nothrow_move_constructible_v<A> is true and is_nothrow_move_assignable_v<A> is true .
|
|
A::element_type
|
A::element_type is required to be a complete object type that is not an abstract class type.
|
|
A::pointer
|
A::pointer 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 .
|
|
A::reference
|
Constraints: 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 .
|
|
A::offset_policy
|
Accessor policy for accessing a pointer returned by a.offset(p,i) . Constraints: — A::offset_policy meets the requirements of an accessor policy in Table �, — is_convertible_v<A, A::offset_policy> is true , and — A::offset_policy can be constructed from a .
|
|
a.access(p, i)
|
A::reference
|
Returns: An object which provides access to the i -th element in the range of elements that starts at p .
|
a.offset(p, i)
|
A::offset_policy::pointer
|
Returns: A handle to the range of objects which that starts at the i -th element of the objects p is a handle for.
|
22.7.�.2 Class template default_accessor
[mdspan.accessor.default]
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.
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
(pointer p, size_t i) const noexcept;
offset
constexpr reference access(pointer p, size_t i) const noexcept;
};
}
22.7.�.2 Class template default_accessor
members [mdspan.accessor.members]
template<class OtherElementType>
constexpr default_accessor(default_accessor<OtherElementType>) noexcept {}
1 Constraints:
is_convertible_v<typename default_accessor<OtherElementType>::element_type(*)[], element_type(*)[]>
is true
.constexpr typename offset_policy::pointer
(pointer p, size_t i) const noexcept; offset
constexpr reference access(pointer p, size_t i) const noexcept;
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.
4 As with span
, the storage of the objects in the codomain of an mdspan
is owned by some other object.
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_type<extents_type>;
using element_type = ElementType;
using value_type = remove_cv_t<element_type>;
using size_type = size_t ;
using difference_type = ptrdiff_t;
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,
>& other);
OtherLayoutPolicy, OtherAccessorPolicy
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 accessor_type accessor() const { return acc_; }
static constexpr int rank() { return Extents::rank(); }
static constexpr int rank_dynamic() { return Extents::rank_dynamic(); }
static constexpr size_type static_extent(size_t r) { return Extents::static_extent(r); }
constexpr Extents 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 pointer data() const { return ptr_; }
constexpr 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:
// exposition only
accessor_type acc_; // exposition only
mapping_type map_; {}; // exposition only
pointer ptr_};
}
5 mdspan<ElementType, Extents, LayoutPolicy, AccessorPolicy>
is a trivially copyable type if AccessorPolicy
, LayoutPolicy::mapping_type<Extents>
and AccessorPolicy::pointer
are trivially copyable types. mdspan<ElementType, Extents, LayoutPolicy, AccessorPolicy>
is a default constructible type if AccessorPolicy
and LayoutPolicy::mapping_type<Extents>
are default constructible 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 (cv-unqualified) specialization of extents
, then the program is ill-formed.
8 If LayoutPolicy
does not meet the layout mapping policy requirements, then the program is ill-formed.
9 If AccessorPolicy
does not meet the accessor policy requirements or 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 ptr, SizeTypes... exts);
template<class SizeType, size_t N>
explicit(N != rank_dynamic())
constexpr mdspan(pointer p, const array<SizeType, N>& exts);
3 Constraints:
4 Effects: Equivalent to: mdspan(p, exts[Rs]...)
, with Rs...
from index_sequence<Rs...>
matching make_index_sequence<N>
.
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);
9Effects:
template<class OtherElementType, class OtherExtents,
class OtherLayoutPolicy, class OtherAccessor>
explicit(see below)
constexpr mdspan(const mdspan<OtherElementType, OtherExtents,
>& other); OtherLayoutPolicy, OtherAccessor
10 Constraints:
(10.1) is_constructible_v<mapping_type, OtherLayoutPolicy::template mapping<OtherExtents>
is true
;
(10.2) is_constructible_v<Accessor, OtherAccessor>
is true
;
(10.3) is_constructible_v<pointer, OtherAccessor::pointer>
is true
;
(10.4) OtherExtents::rank() == rank()
is true
; and
(10.5) For all r
in the range [0, rank())
, if other.static_extent(r) != dynamic_extent && static_extent(r) != dynamic_extent
is true
, then other.static_extent(r) == static_extent(r)
is true
.
11 Preconditions: For all r
, static_extent(r) == dynamic_extent || static_extent(r) == other.extent(r)
is true
.
12 Effects:
13 Remarks: The expression inside explicit
is equivalent to: !std::is_convertible_v<typename OtherLayoutPolicy::mapping_type, mapping_type> || !std::is_convertible_v<OtherAccessorPolicy, AccessorPolicy> || !std::is_convertible_v<typename OtherAccessorPolicy::pointer, pointer>
22.7.�.2 mdspan
members [mdspan.mdspan.members]
template<class... SizeTypes>
constexpr reference operator[](SizeTypes... indices) const;
1 Constraints:
2 Preconditions: acc_.access(ptr_, map_(indices...))
shall be valid.
3 Effects: Equivalent to: return acc_.access(ptr_, map_(indices...));
.
template<class SizeType, size_t N>
constexpr reference operator[](const array<SizeType, N>& indices) const;
constexpr size_type size() const;
extent(r)
for all r
in the range [0, 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 mdspan<ElementType, see below, see below,
typename AccessorPolicy::offset_policy>
(const mdspan<ElementType, Extents, LayoutPolicy,
submdspan>& src, SliceSpecifiers... slices);
AccessorPolicy}
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 Define map_rank
as an array<size_t,src.rank()>
such that for all j
in the range [0, src.rank())
map_rank[j]
equals dynamic_extent
if is_convertible_v<
Sj,size_t>
is true
, or else map_rank[j]
equals the number of Sk with k < j such that is_convertible_v<
Sk,tuple<size_t,size_t>> || is_convertible_v<
Sk,full_extent_t>
is true
.
5 Let first
and last
be exposition-only variables of type array<size_t,src.rank()>
. For r in the range [0, src.rank()
), define the values of first[r]
and last[r]
as follows:
is_convertible_v<
Sr,size_t>
, then first[r]
equals sr, and last[r]
equals first[r]
+ 1;is_convertible_v<
Sr,tuple<size_t,size_t>>
, 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>
;is_convertible_v<
Sr,full_extent_t>
, then first[r]
equals 0
, and last[r]
equals src.extent(r)
.6 Constraints:
sizeof(slices...)
equals src.rank()
,LayoutPolicy
is layout_left
, layout_right
, layout_stride
, or any type in a possibly empty set of implementation-defined types, each of which meets the requirements of a layout mapping policy [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]; andk
in the range [0, src.rank())
, 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 Preconditions:
r
< src.rank()
, 0 <= first[r] && first[r] <= last[r] && last[r] <= src.extent(r)
is true
.8 Ensures: All of the following:
sub.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
.i...
denote a multidimensional index in the domain of src
with ik denoting the k-th element of i...
, such that ik is greater than or equal to first[k]
and ik is less than last[k]
for all k
in the range [0,src.rank()
). Let the pack j...
denote a multidimensional index in the domain of sub
with js denoting the s-th element of j...
, such that js is equal to ik minus first[k]
where map_rank[k]
equals s
for all s
in the range [0,sub.rank()
). Then sub(j...)
and src(i...)
refer to the same element in the codomain of src
.k
< src.rank()
, if map_rank[k] != dynamic_extent
is true
, then sub.extent(map_rank[k])
equals last[k] - first[k]
.src.is_strided()
is true
, then sub.is_strided()
is true
, and for all k
in the range [0, src.rank())
, if map_rank[k] != dynamic_extent
is true
, then sub.stride(map_rank[k])
equals src.stride(k)
.k
in the range [0, src.rank())
, if map_rank[k] != dynamic_extent
is true
and src.static_extent(k)
does not equal dynamic_extent
and is_convertible_v<
Sk,full_extent_t>
is true
, then sub.static_extent(map_rank[k])
equals src.static_extent(k)
.LayoutPolicy
is layout_left
and sub.rank() > 0
is true
, then:
is_convertible_v<
Sk,full_extent_t>
is true
for all k
in the range [0,sub.rank()-1
) and is_convertible_v<
Sk,size_t>
is false
for k
equal sub.rank()-1
, then decltype(sub)::layout_type
is layout_left
, otherwisedecltype(sub)::layout_type
is layout_stride
.LayoutPolicy
is layout_left
and sub.rank()
is 0
, then decltype(sub)::layout_type
is layout_left
.LayoutPolicy
is layout_right
and sub.rank() > 0
is true
, then:
is_convertible_v<
Sk,full_extent_t>
is true
for all k
in the range [src.rank()-sub.rank()+1
,src.rank()
) and is_convertible_v<
Sk,size_t>
is false
for k
equal src.rank()-sub.rank()
, then decltype(sub)::layout_type
is layout_right
, elsedecltype(sub)::layout_type
is layout_stride
.LayoutPolicy
is layout_right
and sub.rank()
is 0
, then decltype(sub)::layout_type
is layout_right
.LayoutPolicy
is layout_stride
, then decltype(sub)::layout_type
is layout_stride
.9 Effects:
sub.acc_
with src.accessor()
.sub.ptr_
with src.accessor().offset(src.data(),apply(src.mapping(),first))
.sub.map_
in a way that above conditions are satisfied.[Note:* Example of submdspan
use:]
We would like LEWG to poll on sending P0009 (‘mdspan’) to LWG for C++23.
There is an mdspan implementation available at https://github.com/kokkos/mdspan/.
The original version of this paper, N4355, predates the “P” naming for papers.
Related papers:
mdspan
codomain concept of span is well-aligned with this paper.AccessorPolicy
extension point in this proposal is intended to include such memory access properties.mdspan
Included proposed modification of span
to better align span
with mdspan
.span
for the future Proposed modification of span
mdspan
and span
mdspan
mdspan
mdspan
and CTAD