1. Authors and contributors
1.1. Authors
-
Mark Hoemmen ([email protected]) (NVIDIA)
-
Christian Trott ([email protected]) (Sandia National Laboratories)
-
Damien Lebrun-Grandie ([email protected]) (Oak Ridge National Laboratory)
-
Malte Förster ([email protected]) (NVIDIA)
-
Jiaming Yuan ([email protected]) (NVIDIA)
2. Revision history
-
Revision 0 submitted 2022-09-14
-
Revision 1 submitted 2022-10-15
-
Change padding stride to function as an overalignment factor if less than the extent to pad. Remove mapping constructor that takes
andextents_type
, because the latter may not be the actual padding stride.extents < index_type , padding_stride > -
Make converting constructors from
tolayout_ { left , right } _padded :: mapping
use Mandates rather than Constraints to check compile-time stride compatibility.layout_ { left , right } :: mapping -
Mandate that
's actual padding stride, if known at compile time, be representable as a value of typelayout_ { left , right } _padded :: mapping
(as well as of typeindex_type
, the previous requirement).size_t -
Add section explaining why we don’t permit conversion from more aligned to less aligned.
-
Fixed typos in Wording
-
Fix formatting in non-Wording, and add links for BLAS and LAPACK
-
3. Proposed changes and justification
3.1. Summary of proposed changes
We propose two new mdspan layouts,
and
.
These layouts support two use cases:
-
array layouts that are contiguous in one dimension, as supported by commonly used libraries like the BLAS (Basic Linear Algebra Subroutines; see P1417 and P1674 for historical overview and references) and LAPACK (Linear Algebra PACKage); and
-
"padded" storage for overaligned access of the start of every contiguous segment of the array.
We also propose changing
of a
resp.
mdspan
to return
resp.
instead of
, when the slice arguments permit it.
3.2. Two new mdspan layouts
The two new mdspan layouts
and
are strided, unique layouts.
If the rank is zero or one,
then the layouts behave exactly like
resp.
.
If the rank is two or more,
then the layouts implement a special case of
where only one stride may differ from the extent
that in
resp.
would completely define the stride.
We call that stride the padding stride,
and the extent that in
resp.
would define it the extent to pad.
The padding stride of
is
,
and the extent to pad is
.
The padding stride of
is
,
and the extent to pad is
.
All other strides of
are the same as in
,
and all other strides of
are the same as in
.
3.2.1. Optimizations over layout_stride
The two new layouts offer the following optimizations over
.
-
They guarantee at compile time that one extent always has stride-1 access. While
's member functions are alllayout_stride
, its mapping constructor takes the strides as aconstexpr
withstd :: array
size.rank () -
They do not need to store any strides if the padding stride is known at compile time. Even if the padding stride is a run-time value, these layouts only need to store the one stride value (as
). Theindex_type
class must store alllayout_stride :: mapping
stride values.rank ()
3.2.2. New layouts unify two use cases
The proposed layouts unify two different use cases:
-
overaligned access to the beginning of each contiguous segment of elements, and
-
representing exactly the data layout assumed by the General (GE) matrix type in the BLAS' C binding.
Regarding (1), an appropriate choice of padding can ensure any desired overalignment of the beginning of each contiguous segment of elements in an mdspan, as long as the entire memory allocation has the same overalignment. This is useful for hardware features that require or perform better with overaligned access, such as SIMD (Single Instruction Multiple Data) instructions.
Regarding (2), the padding stride is the same as
BLAS' "leading dimension" of the matrix (
) argument.
Unlike
and
,
any subview of a contiguous subset of rows and columns
of a rank-2
or
mdspan
preserves the layout.
For example, if
is a rank-2 mdspan
whose layout is
,
then
also has layout
with the same padding stride as before.
The BLAS and algorithms that use it
(such as the blocked algorithms in LAPACK)
depend on this ability to operate on contiguous submatrices
with the same layout as their parent.
For this reason, we can replace the
layout in P1673R9 with
and
.
Making most effective use of the new layouts in code that uses P1673
calls for integrating them with
.
This is why we propose the following changes as well.
3.2.3. Design change from R0
A design change from R0 of this paper makes this overalignment case
easier to use and more like the existing
interface.
In R0 of this paper, the user’s padding input parameter
(either a compile-time
or a run-time value)
was exactly the padding stride.
As such, it had to be greater than or equal to the extent to pad.
For example, if users had an
of 13
and wanted to overalign the corresponding
to a multiple of 4,
they would have had to specify
.
This was inconsistent with
,
whose template argument (the byte alignment)
would need to be
.
Also, users who wanted a compile-time padding stride
would have needed to compute it themselves
from the corresponding compile-time extent,
rather than prespecifying a fixed overalignment factor
that could be used for any extent.
This was not only harder to use, but it made the layout itself
(not just the layout mapping) depend on the extent.
That was inconsistent with the existing mdspan layouts,
where the layout type itself (e.g.,
)
is always a function from
specialization to layout mapping.
In this version of the paper,
we interpret the case where the input padding stride
is less than the extent to pad
as an "overalignment factor" instead of a stride.
To revisit the above example,
would take an
of 13
and round up the corresponding
to 16.
However, as before,
would take an
of 13
and round up the corresponding
to 17.
The rule is consistent: the actual padding stride is always
the next multiple of the input padding stride
greater than or equal to the extent-to-pad.
In R0 of this paper, the following alias
using overaligned_matrix_t = mdspan < float , dextents < size_t , 2 > , layout_right_padded < 4 >> ;
would only be meaningful if the run-time extents are less than or equal to 4. In this version of the paper, this alias would always mean "the padding stride rounds up the rightmost extent to a multiple of 4, whatever the extent may be." R0 had no way to express that use case with a compile-time input padding stride. This is important for hardware features and compiler optimizations that require overalignment of multidimensional arrays.
3.2.4. Padding stride equality for layout mapping conversions
has a converting constructor from
.
Similarly,
has a converting constructor from
.
These constructors require, among other conditions,
that if
and
do not equal
,
then
equals
.
Users may ask why they can’t convert a more overaligned mapping,
such as
,
to a less overaligned mapping, such as
.
The problem is that this may not be correct for all extents.
For example, the following code would be incorrect
if it were well formed (it is not, in this proposal).
layout_left_padded < 4 >:: mapping m_orig { extents { 9 , 2 }}; layout_left_padded < 2 >:: mapping m_new ( m_orig );
The issue is that
has an underlying ("physical") layout of
,
but
would have an underlying layout of
.
That is,
is 12,
but
is 10.
In case one is tempted to permit
assigning dynamic padding stride to static padding stride,
the following code would also be incorrect
if it were well formed (it is not, in this proposal).
Again,
is 12.
layout_left_padded < dynamic_extent >:: mapping m_orig { extents { 9 , 2 }, 4 }; layout_left_padded < 2 >:: mapping m_new ( m_orig );
The following code is well formed in this proposal,
and it gives
the expected original padding stride of 12.
layout_left_padded < dynamic_extent >:: mapping m_orig { extents { 9 , 2 }, 4 }; layout_left_padded < dynamic_extent >:: mapping m_new ( m_orig );
Similarly, the following code is well formed in this proposal,
and it gives
the expected original padding stride of 12.
layout_left_padded < 4 >:: mapping m_orig { extents { 9 , 2 }}; layout_left_padded < dynamic_extent >:: mapping m_new ( m_orig );
3.3. Integration with submdspan
We propose changing
(see P2630)
of a
resp.
mdspan
to return
resp.
instead of
, if the slice arguments permit it.
Taking the
of a
resp.
mdspan
will preserve the layout, again if the slice arguments permit it.
The phrase "if the slice arguments permit it" means the following.
3.3.1. layout_left_padded
and layout_left
cases
In what follows, let
be the following function,
template < class Elt , class Extents , class Layout , class Accessor , class S0 , class S1 > requires ( is_convertible_v < S0 , tuple < typename Extents :: index_type , typename Extents :: index_type >> && is_convertible_v < S1 , tuple < typename Extents :: index_type , typename Extents :: index_type >> ) auto left_submatrix ( mdspan < Elt , Extents , Layout , Accessor > X , S0 s0 , S1 s1 ) { auto full_extents = [] < size_t ... Indices > ( index_sequence < Indices ... > ) { return tuple { ( Indices , full_extent )... }; }( make_index_sequence < X . rank () - 2 > ()); return apply ( [ & ]( full_extent_t ... fe ) { return submdspan ( X , s0 , s1 , fe ...); }, full_extents ); }
let
be an integral type,
let
be an object of a type
such that
is true
,
and let
be an object of a type
such that
is true
.
Let
be an
with rank at least two
with
naming the same type as
,
whose layout is
for some
.
Let
be the object returned from
.
Then,
is an
of rank
with layout
,
and
equals
.
Let
be an
with rank at least two
with
naming the same type as
,
whose layout is
.
Let
be the object returned from
.
Then,
is an
of rank
with layout
,
where
is
-
, ifsrm1_val1 - srm1_val0
is convertible tosrm1
withtuple < integral_constant < decltype ( W ) :: index_type , srm1_val0 > , integral_constant < decltype ( W ) :: index_type , srm1_val1 >>
greater than to equal tosrm1_val1
; else,srm1_val0 -
.dynamic_rank
Also,
equals
.
3.3.2. layout_right_padded
and layout_right
cases
In what follows, let
be the following function,
template < class Elt , class Extents , class Layout , class Accessor , class Srm2 , class Srm1 > requires ( is_convertible_v < Srm2 , tuple < typename Extents :: index_type , typename Extents :: index_type >> && is_convertible_v < Srm1 , tuple < typename Extents :: index_type , typename Extents :: index_type >> ) auto left_submatrix ( mdspan < Elt , Extents , Layout , Accessor > X , Srm2 srm2 , Srm1 srm1 ) { auto full_extents = [] < size_t ... Indices > ( index_sequence < Indices ... > ) { return tuple { ( Indices , full_extent )... }; }( make_index_sequence < X . rank () - 2 > ()); return apply ( [ & ]( full_extent_t ... fe ) { return submdspan ( X , fe ..., srm2 , srm1 ); }, full_extents ); }
let
("s of rank minus 2") be an object of a type
such that
is true
,
and let
("s of rank minus 1") be an object of a type
such that
is true
.
Similarly, let
be an
with rank at least two
whose layout is
for some
.
Let
name the type
.
Let
("S of rank minus 2") be an object of a type
such that
is true
,
and let
("S of rank minus 1") be an object of a type
such that
is true
.
In the following code fragment,
auto full_extents = [] < size_t ... Indices > ( index_sequence < Indices ... > ) { return tuple { ( Indices , full_extent )... }; }( make_index_sequence < Y . rank () - 2 > ()); auto Y_sub = apply ( [ & ]( full_extent_t ... fe ) { return submdspan ( Y , fe ..., srm2 , srm1 ); }, full_extents );
is an
of rank
with layout
,
and
equals
.
Let
be an
with rank at least two whose layout is
.
Let
name the type
.
Let
be an object of a type
such that
is true
,
and let
be an object of a type
such that
is true
.
In the following code fragment,
auto full_extents = [] < size_t ... Indices > ( index_sequence < Indices ... > ) { return tuple { ( Indices , full_extent )... }; }( make_index_sequence < Z . rank () - 2 > ()); auto Z_sub = apply ( [ & ]( full_extent_t ... fe ) { return submdspan ( Z , s0 , s1 , fe ...); }, full_extents );
is an
of rank
with layout
,
where
is
if
is convertible to
with
greater than to equal to
.
Also,
equals
.
Similarly, let
be an
with rank at least two whose layout is
.
Let
name the type
.
Let
("S of rank minus 2") be an object of a type
such that
is true
,
and let
("S of rank minus 1") be an object of a type
such that
is true
.
In the following code fragment,
auto full_extents = [] < size_t ... Indices > ( index_sequence < Indices ... > ) { return tuple { ( Indices , full_extent )... }; }( make_index_sequence < W . rank () - 2 > ()); auto W_sub = apply ( [ & ]( full_extent_t ... fe ) { return submdspan ( W , fe ..., srm2 , srm1 ); }, full_extents );
is an
of rank
with layout
,
where
is
if
is convertible to
with
greater than to equal to
.
Also,
equals
.
Preservation of these layouts under
is an important feature for our linear algebra library proposal P1673,
because it means that for existing BLAS and LAPACK use cases,
if we start with one of these layouts,
we know that we can implement fast linear algebra algorithms
by calling directly into an optimized C or Fortran BLAS.
3.4. Examples
3.4.1. Directly call C BLAS without checks
We show examples before and after this proposal of functions
that compute the matrix-matrix product
.
The
function computes this product recursively,
by partitioning each of the three matrices into a 2 x 2 block matrix
using the
function.
When the
matrix is small enough,
stops recursing
and instead calls a
function
with different overloads for different matrix layouts.
If the matrix layouts support it,
can call the C BLAS function
directly on the
s' data.
This is fast if the C BLAS is optimized.
Otherwise,
falls back to a slow generic implementation.
This example is far from ideally optimized, but it hints at the kind of optimizations that linear algebra computations do in practice.
Common code:
template < class Layout > using out_matrix_view = mdspan < float , dextents < int , 2 > , Layout > ; template < class Layout > using in_matrix_view = mdspan < const float , dextents < int , 2 > , Layout > ; // Before this proposal, if Layout is layout_left or layout_right, // the returned mdspan would all be layout_stride. // After this proposal, the returned mdspan would be // layout_left_padded resp. layout_right_padded. template < class ElementType , class Layout > auto partition ( mdspan < ElementType , dextents < int , 2 > , Layout > A ) { auto M = A . extent ( 0 ); auto N = A . extent ( 1 ); auto A00 = submdspan ( A , tuple { 0 , M / 2 }, tuple { 0 , N / 2 }); auto A01 = submdspan ( A , tuple { 0 , M / 2 }, tuple { N / 2 , N }); auto A10 = submdspan ( A , tuple { M / 2 , M }, tuple { 0 , N / 2 }); auto A11 = submdspan ( A , tuple { M / 2 , M }, tuple { N / 2 , N }); return tuple { A00 , A01 , A10 , A11 }; } template < class Layout > void recursive_matrix_product ( in_matrix_view < Layout > A , in_matrix_view < Layout > B , out_matrix_view < Layout > C ) { // Some hardware-dependent constant constexpr int recursion_threshold = 16 ; if ( std :: max ( C . extent ( 0 ) || C . extent ( 1 )) <= recursion_threshold ) { base_case_matrix_product ( A , B , C ); } else { auto [ C00 , C01 , C10 , C11 ] = partition ( C ); auto [ A00 , A01 , A10 , A11 ] = partition ( A ); auto [ B00 , B01 , B10 , B11 ] = partition ( B ); recursive_matrix_product ( A00 , B00 , C00 ); recursive_matrix_product ( A01 , B10 , C00 ); recursive_matrix_product ( A10 , B00 , C10 ); recursive_matrix_product ( A11 , B10 , C10 ); recursive_matrix_product ( A00 , B01 , C01 ); recursive_matrix_product ( A01 , B11 , C01 ); recursive_matrix_product ( A10 , B01 , C11 ); recursive_matrix_product ( A11 , B11 , C11 ); } } // Slow generic implementation template < class Layout > void base_case_matrix_product ( in_matrix_view < Layout > A , in_matrix_view < Layout > B , out_matrix_view < Layout > C ) { for ( size_t j = 0 ; j < C . extent ( 1 ); ++ j ) { for ( size_t i = 0 ; i < C . extent ( 0 ); ++ i ) { typename out_matrix_view < Layout >:: value_type C_ij {}; for ( size_t k = 0 ; k < A . extent ( 1 ); ++ k ) { C_ij += A ( i , k ) * B ( k , j ); } C ( i , j ) += C_ij ; } } }
A user might interpret
as "column major,"
and therefore "the natural layout to pass into the BLAS."
void base_case_matrix_product ( in_matrix_view < layout_left > A , in_matrix_view < layout_left > B , out_matrix_view < layout_left > C ) { cblas_sgemm ( CblasColMajor , CblasNoTrans , CblasNoTrans , C . extent ( 0 ), C . extent ( 1 ), A . extent ( 1 ), 1.0f , A . data_handle (), A . stride ( 1 ), B . data_handle (), B . stride ( 1 ), 1.0f , C . data_handle (), C . stride ( 1 )); }
However,
never gets to use
the
overload of
,
because the base case matrices are always
.
On discovering this, the author of these functions
might be tempted to write a custom layout for "BLAS-compatible" matrices.
However, the
proposal P2630R0 currently forces
to return four
mdspan
if given a
(or
) input mdspan.
This would, in turn, force users of
to commit to a custom layout, if they want to use the BLAS.
Alternately, the author of these functions could specialize
for
, and check whether
,
, and
are all equal to one
before calling
.
However, that would force extra run-time checks for a use case
that most users might never encounter,
because most users are starting with
matrices
or contiguous submatrices thereof.
After our proposal, the author can specialize
for exactly the layout supported by the BLAS.
They could even get rid of the fall-back implementation
if users never exercise it.
template < size_t p > void base_case_matrix_product ( in_matrix_view < layout_left_padded < p >> A , in_matrix_view < layout_left_padded < p >> B , out_matrix_view < layout_left_padded < p >> C ) { // same code as above cblas_sgemm ( CblasColMajor , CblasNoTrans , CblasNoTrans , C . extent ( 0 ), C . extent ( 1 ), A . extent ( 1 ), 1.0f , A . data_handle (), A . stride ( 1 ), B . data_handle (), B . stride ( 1 ), 1.0f , C . data_handle (), C . stride ( 1 )); }
3.4.2. Overaligned access
By combining these new layouts with an accessor that ensures overaligned access, we can create an mdspan for which the beginning of every contiguous segment of elements is overaligned by some given factor. This can enable use of hardware features that require overaligned memory access.
The following
class template
(which this proposal does not propose to add to the C++ Standard Library)
uses the C++ Standard Library function
to decorate pointer access.
template < class ElementType , std :: size_t byte_alignment > struct aligned_accessor { // Even if a pointer p is aligned, p + i might not be. using offset_policy = std :: default_accessor < ElementType > ; using element_type = ElementType ; using reference = ElementType & ; // Some implementations might have an easier time optimizing // if this class applies an attribute to the pointer type. // Examples of attributes include // __declspec(align_value(byte_alignment)) // and // __attribute__((align_value(byte_alignment))). using data_handle_type = ElementType * ; constexpr aligned_accessor () noexcept = default ; // A feature of default_accessor that permits // conversion from nonconst to const. template < class OtherElementType , std :: size_t other_byte_alignment > requires ( std :: is_convertible_v < OtherElementType ( * )[], element_type ( * )[] > && other_byte_alignment == byte_alignment ) constexpr aligned_accessor ( aligned_accessor < OtherElementType , other_byte_alignment > ) noexcept {} constexpr reference access ( data_handle_type p , size_t i ) const noexcept { return std :: assume_aligned < byte_alignment > ( p )[ i ]; } constexpr typename offset_policy :: data_handle_type offset ( data_handle_type p , size_t i ) const noexcept { return p + i ; } };
We include some helper functions for making overaligned array allocations.
template < class ElementType > struct delete_raw { void operator ()( ElementType * p ) const { std :: free ( p ); } }; template < class ElementType > using allocation_t = std :: unique_ptr < ElementType [], delete_raw < ElementType >> ; template < class ElementType , std :: size_t byte_alignment > allocation_t < ElementType > allocate_raw ( const std :: size_t num_elements ) { const std :: size_t num_bytes = num_elements * sizeof ( ElementType ); void * ptr = std :: aligned_alloc ( byte_alignment , num_bytes ); return { ptr , delete_raw < ElementType > {}}; }
Now we can show our example.
This 15 x 17 matrix of
will have extra padding so that
every column is aligned to
bytes.
We can use the layout mapping to determine
the required storage size (including padding).
Users can then prove at compile time
that they can use special hardware features
that require overaligned access
and/or assume that the padding element
at the end of each column is accessible memory.
constexpr std :: size_t element_alignment = 8 ; constexpr std :: size_t byte_alignment = element_alignment * sizeof ( float ); using layout_type = layout_left_padded < element_alignment > ; layout_type :: mapping mapping { dextents < int , 2 > { 15 , 17 }}; auto allocation = allocate_raw < float , byte_alignment > ( mapping . required_span_size ()); using accessor_type = aligned_accessor < float , byte_alignment > ; mdspan m { allocation . get (), mapping , accessor_type {}}; // m_sub has the same layout as m, // and each column of m_sub has the same overalignment. auto m_sub = submdspan ( m , tuple { 0 , 11 }, tuple { 1 , 13 });
3.5. Alternatives
We considered a variant of
that could encode
any combination of compile-time or run-time strides in the layout type.
This could, for example, use the same mechanism that
uses.
(The reference implementation calls this mechanism a "partially static array.")
However, we rejected this approach as overly complex for our design goals.
First, the goal of
isn’t to insist even harder
that the compiler bake constants into
evaluation.
The goal is to communicate compile-time information to users.
The most benefit comes not just from knowing the padding stride at compile time,
but also from knowing that one dimension always uses stride-one (contiguous) storage.
Putting these two pieces of information together
lets users apply compiler annotations like
,
as in the above
example.
Knowing that one dimension always uses contiguous storage
also tells users that they can pass the mdspan’s data
directly into C or Fortran libraries like the BLAS or LAPACK.
Users can benefit from this even if the padding stride is a run-time value.
Second, the
annotations in the existing layout mappings
mean that users might be evaluating
fully at compile time. The reference mdspan implementation has several tests that demonstrate this by using the result of a layout mapping evaluation
in a context where it needs to be known at compile time.
Third, the performance benefit of storing
or
mdspan.
In that case, the representation of the strides
that preserves the most compile-time information
would be just the original mdspan’s
object.
(Compare to the exposition-only
.)
Computing each stride would then call for a forward (for
)
or reverse (for
) product of the original mdspan’s extents.
As a result, any stride to the right resp. left of a run-time extent
would end up depending on that run-time extent anyway.
The larger the rank, the more strides get "touched" by run-time information.
Fourth, a strided mdspan that can represent layouts as general as
,
but has entirely compile-time extents and strides,
could be useful for supporting features of a specific computer architecture.
However, these hardware features would probably have limitations
that would prevent them from supporting general strided layouts anyway.
For example, they might require strides to be a power of two,
or they might be limited to specific ranges of extents or strides.
These limitations would call for custom implementation-specific layouts,
not something as general as a "compile-time
."
3.6. Implementation experience
Pull request 180 in the reference mdspan implementation implements most of this proposal.
Next steps are to add constructors to the existing layout mappings,
and to add
support for the new layouts.
3.7. Desired ship vehicle
C++26 / IS.
4. Wording
Text in blockquotes is not proposed wording, but rather instructions for generating proposed wording. The � character is used to denote a placeholder section number which the editor shall determine. First, apply all wording from P2630R0. (This proposal is a "rebase" atop the changes proposed by P2630R0.)
Add the following feature test macro to [version.syn], replacing YYYYMML with the integer literal encoding the appropriate year (YYYY) and month (MM).
#define __cpp_lib_mdspan_layout_padded YYYYMML // also in <mdspan>
In Section � [mdspan.syn], after
, add the following:
struct layout_stride ;
template < size_t padding_stride = dynamic_extent > struct layout_left_padded ; template < size_t padding_stride = dynamic_extent > struct layout_right_padded ;
In Section � [mdspan.layout.left.overview] ("Overview"), add the following constructor to the
class declaration, between the constructor converting from
layout_left :: mapping and the constructor converting from
layout_right :: mapping < OtherExtents > :
layout_stride :: mapping < OtherExtents >
template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( not is_convertible_v < OtherExtents , extents_type > ) mapping ( const layout_left_padded < other_padding_stride >:: mapping < OtherExtents >& ) noexcept ;
In Section � [mdspan.layout.left.cons] ("Constructors"), add the following between the constructor converting from
and the constructor converting from
layout_right :: mapping < OtherExtents > :
layout_stride :: mapping < OtherExtents >
template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( not is_convertible_v < OtherExtents , extents_type > ) mapping ( const layout_left_padded < other_padding_stride >:: mapping < OtherExtents >& other ) noexcept ;
Constraints:
is true
.
Mandates: If
-
is greater than one,Extents :: rank () -
does not equalExtents :: static_extent ( 0 )
,dynamic_extent -
does not equalOtherExtents :: static_extent ( 0 )
, anddynamic_extent -
does not equalother_padding_stride
,dynamic_extent
then the least multiple of
greater than or equal to
-
is representable as a value of type
, andExtents :: index_type -
equals
.Extents :: static_extent ( 0 )
Preconditions:
-
If
is greater than one, thenOtherExtents :: rank ()
is representable as a value of typeother . stride ( 1 )
;index_type -
if
isextents_type :: rank () > 0 true
, then for all $r$ in the range [0,
),extents_type :: rank ()
$r$other . stride (
equals) extents (). fwd - prod - of - extents
$r$(
, and) -
is representable as a value of typeother . required_span_size ()
([basic.fundamental]).index_type
Effects: Direct-non-list-initializes
with
.
In Section � [mdspan.layout.right.overview] ("Overview"), add the following constructor to the
class declaration, between the constructor converting from
layout_right :: mapping and the constructor converting from
layout_left :: mapping < OtherExtents > :
layout_stride :: mapping < OtherExtents >
template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( not is_convertible_v < OtherExtents , extents_type > ) mapping ( const layout_right_padded < other_padding_stride >:: mapping < OtherExtents >& ) noexcept ;
In Section � [mdspan.layout.right.cons] ("Constructors"), add the following between the constructor converting from
and the constructor converting from
layout_left :: mapping < OtherExtents > :
layout_stride :: mapping < OtherExtents >
template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( not is_convertible_v < OtherExtents , extents_type > ) mapping ( const layout_right_padded < other_padding_stride >:: mapping < OtherExtents >& other ) noexcept ;
Constraints:
is true
.
Mandates: If
-
is greater than one,Extents :: rank () -
does not equalExtents :: static_extent ( Extents :: rank () - 1 )
,dynamic_extent -
does not equalOtherExtents :: static_extent ( Extents :: rank () - 1 )
, anddynamic_extent -
does not equalother_padding_stride
,dynamic_extent
then the least multiple of
greater than or equal to
-
is representable as a value of type
, andExtents :: index_type -
equals
.Extents :: static_extent ( Extents :: rank () - 1 )
Preconditions:
-
If
is greater than one, thenOtherExtents :: rank ()
is representable as a value of typeother . stride ( OtherExtents :: rank () - 2 )
;index_type -
if
isextents_type :: rank () > 0 true
, then for all $r$ in the range [0,
),extents_type :: rank ()
$r$other . stride (
equals) extents (). rev - prod - of - extents
$r$(
, and) -
is representable as a value of typeother . required_span_size ()
([basic.fundamental]).index_type
Effects: Direct-non-list-initializes
with
.
After the end of Section � [mdspan.layout.stride], add the following:
4.1. Class template layout_left_padded :: mapping
[mdspan.layout.left_padded]
provides a layout mapping
that behaves like
,
except that the padding stride
can be greater than or equal to
.
Users provide an input padding stride value
either as a
template parameter
of
,
or as a run-time argument of
's constructor.
The padding stride is the least multiple of the input padding stride value
greater than or equal to
.
template < size_t padding_stride = dynamic_extent > struct layout_left_padded { template < class Extents > class mapping { public : using extents_type = Extents ; using index_type = typename extents_type :: index_type ; using size_type = typename extents_type :: size_type ; using rank_type = typename extents_type :: rank_type ; using layout_type = layout_left_padded < padding_stride > ; private : static constexpr size_t < it > actual - padding - stride </ it > = /* see-below */ ; // exposition only using < it > inner - extents - type </ it > = /* see-below */ ; // exposition only using < it > unpadded - extent - type </ it > = /* see-below */ ; // exposition only using < it > inner - mapping - type </ it > = layout_left :: template mapping << it > inner - extents - type </ it >> ; // exposition only < it > inner - mapping - type </ it > < it > inner - mapping_ </ it > ; // exposition only < it > unpadded - extent - type </ it > < it > unpadded - extent_ </ it > ; // exposition only public : constexpr mapping ( const extents_type & ext ); template < class Size > constexpr mapping ( const extents_type & ext , Size padding_value ); template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( /* see below */ ) mapping ( const layout_left_padded < other_padding_stride >:: mapping < OtherExtents >& ); template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( not is_convertible_v < OtherExtents , extents_type > ) mapping ( const layout_right_padded < other_padding_stride >:: mapping < OtherExtents >& ) noexcept ; constexpr mapping ( const mapping & ) noexcept = default ; mapping & operator = ( const mapping & ) noexcept = default ; constexpr extents_type extents () const noexcept ; constexpr std :: array < index_type , extents_type :: rank () > strides () const noexcept ; constexpr index_type required_span_size () const noexcept ; template < class ... Indices > constexpr size_t operator ()( Indices ... idxs ) const noexcept ; static constexpr bool is_always_unique () noexcept { return true; } static constexpr bool is_always_exhaustive () noexcept ; static constexpr bool is_always_strided () noexcept { return true; } static constexpr bool is_unique () noexcept { return true; } constexpr bool is_exhaustive () const noexcept ; static constexpr bool is_strided () noexcept { return true; } constexpr index_type stride ( rank_type r ) const noexcept ; }; };
Throughout this section, let
be the following
size
parameter pack of
:
-
If
equals zero or one, then the empty parameter pack;extents_type :: rank () -
else, the parameter pack
,size_t ( 1 )
, ...,size_t ( 2 )
.extents_type :: rank () - 1
Mandates: If
-
is greater than one,extents_type :: rank () -
does not equalpadding_stride
, anddynamic_extent -
does not equalextents_type :: static_extent ( 0 )
,dynamic_extent
then the least multiple of
that is greater than or equal to
is representable as a value of type
,
and is representable as a value of type
.
static constexpr size_t < it > actual - padding - stride </ it > = /* see-below */ ; // exposition only
-
If
equals zero or one, thenextents_type :: rank ()
.padding_stride -
Else, if
-
does not equalpadding_stride
anddynamic_extent -
does not equalextents_type :: static_extent ( 0 )
,dynamic_extent
then the
value which is the least multiple ofsize_t
that is greater than or equal topadding_stride
.extents_type :: static_extent ( 0 ) -
-
Otherwise,
.dynamic_extent
using < it > inner - extents - type </ it > = /* see-below */ ; // exposition only
-
If
equals zero or one, thenextents_type :: rank () names the typeinner - extents - type
.extents_type -
Otherwise,
names the typeinner - extents - type extents < index_type , actual - padding - stride
., extents_type :: static_extent ( P_left )... >
using < it > unpadded - extent - type </ it > = /* see-below */ ; // exposition only
-
If
equals zero, thenextents_type :: rank () names the typeunpadded - extent - type
.extents < index_type > -
Otherwise,
names the typeunpadded - extent - type
.extents < index_type , extents_type :: static_extent ( 0 ) >
constexpr mapping ( const extents_type & ext );
Preconditions: If
is greater than one
and
does not equal
,
then the least multiple of
greater than to equal to
is representable as a value of type
.
Effects:
-
Direct-non-list-initializes
with:inner - mapping_ -
, ifext
is zero or one; else,extents_type :: rank () -
, ifext . extent ( 0 ), ext . extent ( P_left )...
ispadding_stride
; else,dynamic_extent -
, whereS_left , ext . extent ( P_left )...
is the least multiple ofS_left
greater than or equal topadding_stride
; andext . extent ( 0 )
-
-
if
is zero, value-initializesextents_type :: rank () ; else, direct-non-list-initializesunpadded - extent_ withunpadded - extent_
.ext . extent ( 0 )
template < class Size > constexpr mapping ( const extents_type & ext , Size padding_value );
Constraints:
-
isis_convertible_v < Size , index_type > true
, and -
isis_nothrow_constructible_v < index_type , Size > true
.
Preconditions:
-
If
does not equalpadding_stride
, thendynamic_extent -
is representable as a value of typepadding_value
, andindex_type -
the result of converting
topadding_value
equalsindex_type
.padding_stride
-
-
If
is greater than one, then the least multiple ofextents_type :: rank ()
greater than to equal topadding_value
is representable as a value of typeext . extent ( 0 )
.index_type
Effects:
-
Direct-non-list-initializes
with:inner - mapping_ -
, ifext
is zero or one; else,extents_type :: rank () -
, whereS_left , ext . extent ( P_left )...
is the least multiple ofS_left
greater than or equal topadding_value
; andext . extent ( 0 )
-
-
if
is zero, value-initializesextents_type :: rank () ; else, direct-non-list-initializesunpadded - extent_ withunpadded - extent_
.ext . extent ( 0 )
template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( /* see below */ ) mapping ( const layout_left_padded < other_padding_stride >:: mapping < OtherExtents >& other );
Constraints:
is true
.
Mandates:
is true
.
Preconditions:
-
If
isextents_type :: rank () > 1 true
and
does not equalpadding_stride
, thendynamic_extent
equals the least multiple ofother . stride ( 1 )
greater than or equal topadding_stride extents_type :: index - cast
; and( other . extent ( 0 )) -
is representable as a value of typeother . required_span_size ()
([basic.fundamental]).index_type
Effects:
-
Direct-non-list-initializes
with:inner - mapping_ -
, ifother . extents ()
is zero or one; elseextents_type :: rank () -
; andother . stride ( 1 ), other . extents (). extent ( P_left )...
-
-
if
is zero, value-initializesextents_type :: rank () ; else, direct-non-list-initializesunpadded - extent_ withunpadded - extent_
.other . extents (). extent ( 0 )
Remarks: The expression inside
is equivalent to:
.
template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( not is_convertible_v < OtherExtents , extents_type > ) mapping ( const layout_right_padded < other_padding_stride >:: mapping < OtherExtents >& ) noexcept ;
Constraints:
-
equals zero or one,extents_type :: rank () -
isis_constructible_v < extents_type , OtherExtents > true
.
Precondition:
is representable
as a value of type
([basic.fundamental]).
Effects:
-
Direct-non-list-initializes
withinner - mapping_
; andother . extents () -
if
is zero, value-initializesextents_type :: rank () ; else, direct-non-list-initializesunpadded - extent_ withunpadded - extent_
.other . extents (). extent ( 0 )
[Note: Neither mapping uses the padding stride in the rank-0 or rank-1 case, so the padding stride does not affect either the constraints or the preconditions. — end note]
constexpr extents_type extents () const noexcept ;
Effects:
-
If
is zero, equivalent toextents_type :: rank ()
.return extents_type {}; -
Otherwise, equivalent to
return extents_type ( unpadded - extent_ . extent ( 0 ), inner - mapping_
.. extent ( P_left )...);
constexpr std :: array < index_type , extents_type :: rank () > strides () const noexcept ;
Effects: Equivalent to
.
constexpr index_type required_span_size () const noexcept ;
Effects: Equivalent to
.
template < class ... Indices > constexpr size_t operator ()( Indices ... idxs ) const noexcept ;
Constraints:
-
issizeof ...( Indices ) == Extents :: rank () true
, -
is( std :: is_convertible_v < Indices , index_type > && ...) true
, and -
is( std :: is_nothrow_constructible < index_type , Indices > && ...) true
.
Precondition:
is a multidimensional index in
([mdspan.overview]).
Effects: Let P be a parameter pack such that
is true
.
Equivalent to:
.
[Note: Effects are also equivalent to
return inner - mapping_
( idxs ...);
,
but only after the Precondition has been applied. — end note]
static constexpr bool is_always_exhaustive () noexcept ;
Returns:
-
If
equals zero or one, thenextents_type :: rank () true
; -
else, if neither
nor< it > inner - mapping - type </ it >:: static_extent ( 0 )
equalextents_type :: static_extent ( 0 )
, thendynamic_extent
;< it > inner - mapping - type </ it >:: static_extent ( 0 ) == extents_type :: static_extent ( 0 ) -
otherwise,
false
.
constexpr bool is_exhaustive () const noexcept ;
Returns:
-
If
equals zero, thenextents_type :: rank () true
; -
else,
inner - mapping_ . extent ( 0 ) == unpadded - extent_
.. extent ( 0 )
constexpr index_type stride ( rank_type r ) const noexcept ;
Effects: Equivalent to
.
4.2. Class template layout_right_padded :: mapping
[mdspan.layout.right_padded]
provides a layout mapping
that behaves like
,
except that the padding stride
can be greater than or equal to
.
Users provide an input padding stride value
either as a
template parameter
of
,
or as a run-time argument of
's constructor.
The padding stride is the least multiple of the input padding stride value
greater than or equal to
.
template < size_t padding_stride = dynamic_extent > struct layout_right_padded { template < class Extents > struct mapping { public : using extents_type = Extents ; using index_type = typename extents_type :: index_type ; using size_type = typename extents_type :: size_type ; using rank_type = typename extents_type :: rank_type ; using layout_type = layout_right_padded < padding_stride > ; private : static constexpr size_t < it > actual - padding - stride </ it > = /* see-below */ ; // exposition only using < it > inner - extents - type </ it > = /* see-below */ ; // exposition only using < it > unpadded - extent - type </ it > = /* see-below */ ; // exposition only using < it > inner - mapping - type </ it > = layout_right :: template mapping << it > inner - extents - type </ it >> ; // exposition only < it > inner - mapping - type </ it > < it > inner - mapping_ </ it > ; // exposition only < it > unpadded - extent - type </ it > < it > unpadded - extent_ </ it > ; // exposition only public : constexpr mapping ( const extents_type & ext ); template < class Size > constexpr mapping ( const extents_type & ext , Size padding_value ); template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( /* see below */ ) mapping ( const layout_right_padded < other_padding_stride >:: mapping < OtherExtents >& other ); template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( not is_convertible_v < OtherExtents , extents_type > ) mapping ( const layout_left_padded < other_padding_stride >:: mapping < OtherExtents >& other ) noexcept ; constexpr mapping ( const mapping & ) noexcept = default ; mapping & operator = ( const mapping & ) noexcept = default ; constexpr extents_type extents () const noexcept ; constexpr std :: array < index_type , extents_type :: rank () > strides () const noexcept ; constexpr index_type required_span_size () const noexcept ; template < class ... Indices > constexpr size_t operator ()( Indices ... idxs ) const noexcept ; static constexpr bool is_always_unique () noexcept { return true; } static constexpr bool is_always_exhaustive () noexcept ; static constexpr bool is_always_strided () noexcept { return true; } static constexpr bool is_unique () noexcept { return true; } constexpr bool is_exhaustive () const noexcept ; static constexpr bool is_strided () noexcept { return true; } constexpr index_type stride ( rank_type r ) const noexcept ; }; };
Throughout this section, let
be the following
size
parameter pack of
:
-
If
equals zero or one, then the empty parameter pack;extents_type :: rank () -
else, the parameter pack
,size_t ( 0 )
, ...,size_t ( 1 )
.extents_type :: rank () - 2
Mandates: If
-
is greater than one,extents_type :: rank () -
does not equalpadding_stride
, anddynamic_extent -
does not equalextents_type :: static_extent ( extents_type :: rank () - 1 )
,dynamic_extent
then the least multiple of
that is greater than or equal to
is representable as a value of type
,
and is representable as a value of type
.
static constexpr size_t < it > actual - padding - stride </ it > = /* see-below */ ; // exposition only
-
If
equals zero or one, thenextents_type :: rank ()
.padding_stride -
Else, if
-
does not equalpadding_stride
anddynamic_extent -
does not equalextents_type :: static_extent ( 0 )
,dynamic_extent
then the
value which is the least multiple ofsize_t
that is greater than or equal topadding_stride
.extents_type :: static_extent ( 0 ) -
-
Otherwise,
.dynamic_extent
using < it > inner - extents - type </ it > = /* see-below */ ; // exposition only
-
If
equals zero or one, thenextents_type :: rank () names the typeinner - extents - type
.extents_type -
Otherwise,
names the typeinner - extents - type extents < index_type , extents_type :: static_extent ( P_right )..., actual - padding - stride
.>
using < it > unpadded - extent - type </ it > = /* see-below */ ; // exposition only
-
If
equals zero, thenextents_type :: rank () names the typeunpadded - extent - type
.extents < index_type > -
Otherwise,
names the typeunpadded - extent - type
.extents < index_type , extents_type :: static_extent ( Extents :: rank () - 1 ) >
constexpr mapping ( const extents_type & ext );
Preconditions: If
is greater than one
and
does not equal
,
then the least multiple of
greater than to equal to
is representable as a value of type
.
Effects:
-
Direct-non-list-initializes
with:inner - mapping_ -
, ifext
is zero or one; else,extents_type :: rank () -
, ifext . extent ( P_right )..., ext . extent ( extents_type :: rank () - 1 )
ispadding_stride
; else,dynamic_extent -
, whereext . extent ( P_right )..., S_right
is the least multiple ofS_right
greater than or equal topadding_stride
; andext . extent ( extents_type :: rank () - 1 )
-
-
if
is zero, value-initializesextents_type :: rank () ; else, direct-non-list-initializesunpadded - extent_ withunpadded - extent_
.ext . extent ( extents_type :: rank () - 1 )
template < class Size > constexpr mapping ( const extents_type & ext , Size padding_value );
Constraints:
-
isis_convertible_v < Size , index_type > true
, and -
isis_nothrow_constructible_v < index_type , Size > true
.
Preconditions:
-
If
does not equalpadding_stride
, thendynamic_extent -
is representable as a value of typepadding_value
, andindex_type -
the result of converting
topadding_value
equalsindex_type
.padding_stride
-
-
If
is greater than one, then the least multiple ofextents_type :: rank ()
greater than to equal topadding_value
is representable as a value of typeext . extent ( extents_type :: rank () - 1 )
.index_type
Effects:
-
Direct-non-list-initializes
with:inner - mapping_ -
, ifext
is zero or one; elseextents_type :: rank () -
, whereext . extent ( P_right )..., S_right
is the least multiple ofS_right
greater than or equal topadding_value
; andext . extent ( extents_type :: rank () - 1 )
-
-
if
is zero, value-initializesextents_type :: rank () ; else, direct-non-list-initializesunpadded - extent_ withunpadded - extent_
.ext . extent ( extents_type :: rank () - 1 )
template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( /* see below */ ) mapping ( const layout_right_padded < other_padding_stride >:: mapping < OtherExtents >& other );
Constraints:
is true
.
Mandates:
is true
.
Preconditions:
-
If
isextents_type :: rank () > 1 true
and
does not equalpadding_stride
, thendynamic_extent
equals the least multiple ofother . stride ( extents_type :: rank () - 2 )
greater than or equal topadding_stride extents_type :: index - cast
; and( other . extent ( OtherExtents :: rank () - 1 )) -
is representable as a value of typeother . required_span_size ()
([basic.fundamental]).index_type
Effects:
-
Direct-non-list-initializes
with:inner - mapping_ -
, ifother . extents ()
is zero or one; else,extents_type :: rank () -
; andother . extents (). extent ( P_right )..., other . stride ( extents_type :: rank () - 2 )
-
-
if
is zero, value-initializesextents_type :: rank () ; else, direct-non-list-initializesunpadded - extent_ withunpadded - extent_
.other . extents (). extent ( extents_type :: rank () - 1 )
Remarks: The expression inside
is equivalent to:
.
template < size_t other_padding_stride , class OtherExtents > constexpr explicit ( not is_convertible_v < OtherExtents , extents_type > ) mapping ( const layout_left_padded < other_padding_stride >:: mapping < OtherExtents >& other ) noexcept ;
Constraints:
-
equals zero or one, andextents_type :: rank () -
isis_constructible_v < extents_type , OtherExtents > true
.
Preconditions:
is representable
as a value of type
([basic.fundamental]).
Effects:
-
Direct-non-list-initializes
withinner - mapping_
; andother . extents () -
if
is zero, value-initializesextents_type :: rank () ; else, initializesunpadded - extent_ withunpadded - extent_
.other . extents (). extent ( 0 )
[Note: Neither mapping uses the padding stride in the rank-0 or rank-1 case, so the padding stride does not affect either the constraints or the preconditions. — end note]
constexpr extents_type extents () const noexcept ;
Effects:
-
If
is zero, equivalent toextents_type :: rank ()
.return extents_type {}; -
Otherwise, equivalent to
return extents_type ( inner - mapping_ . extent ( P_right )..., unpadded - extent_
.. extent ( extents_type :: rank () - 1 ));
constexpr std :: array < index_type , extents_type :: rank () > strides () const noexcept ;
Effects: Equivalent to
.
constexpr index_type required_span_size () const noexcept ;
Effects: Equivalent to
.
template < class ... Indices > constexpr size_t operator ()( Indices ... idxs ) const noexcept ;
Constraints:
-
issizeof ...( Indices ) == Extents :: rank () true
, -
is( std :: is_convertible_v < Indices , index_type > && ...) true
, and -
is( std :: is_nothrow_constructible < index_type , Indices > && ...) true
.
Precondition:
is a multidimensional index in
([mdspan.overview]).
Effects: Let
be a parameter pack such that
is true
.
Equivalent to:
.
[Note: Effects are also equivalent to
return inner - mapping_
( idxs ...);
,
but only after the Precondition has been applied. — end note]
static constexpr bool is_always_exhaustive () noexcept ;
Returns:
-
If
equals zero or one,extents_type :: rank () true
; -
else, if neither
nor< it > inner - mapping - type </ it >:: static_extent ( extents_type :: rank () - 1 )
equalextents_type :: static_extent ( extents_type :: rank () - 1 )
, thendynamic_extent
;< it > inner - mapping - type </ it >:: static_extent ( extents_type :: rank () - 1 ) == extents_type :: static_extent ( extents_type :: rank () - 1 ) -
otherwise,
false
.
constexpr bool is_exhaustive () const noexcept ;
Returns:
-
If
equals zero, thenextents_type :: rank () true
; -
else,
inner - mapping_ . extent ( extents_type :: rank () - 1 ) == unpadded - extent_
.. extent ( extents_type :: rank () - 1 )
constexpr index_type stride ( rank_type r ) const noexcept ;
Effects: Equivalent to
.
4.3. Layout specializations of submdspan_mapping
[mdspan.submdspan.mapping]
At the top of Section � [mdspan.submdspan.mapping] ("Layout specializations of
"), before paragraph 1, add the following to the end of the synopsis of specializations.
submdspan_mapping
template < class Extents , std :: size_t padding_stride , class ... SliceSpecifiers > constexpr auto submdspan_mapping ( const layout_left_padded < padding_stride >:: template mapping < Extents >& src , SliceSpecifiers ... slices ) -> see below ; template < class Extents , std :: size_t padding_stride , class ... SliceSpecifiers > constexpr auto submdspan_mapping ( const layout_right_padded < padding_stride >:: template mapping < Extents >& src , SliceSpecifiers ... slices ) -> see below ;
In paragraph 7 (the "Returns" clause) of Section � [mdspan.submdspan.mapping] ("Layout specializations of submdspan_mapping"), replace (7.3) (the
fall-back return type) with the following.
layout_stride
(7.3) Else, if
-
isdecltype ( src ) :: layout_type
;layout_left -
is greater than one;Extents :: rank () -
all the $S_k$ except for $S_0$ and $S_1$ are
;full_extent_t -
$S_0$is_convertible_v <
is, tuple < index_type , index_type >> true
; and -
$S_1$is_convertible_v <
is, tuple < index_type , index_type >> true
;
then
.
(7.4) Else, if
-
isdecltype ( src ) :: layout_type
;layout_right -
is greater than one;Extents :: rank () -
all the $S_k$ except for the two rightmost $S_{r-2}$ and $S_{r-1}$ are
;full_extent_t -
$S_{r-2}$is_convertible_v <
is, tuple < index_type , index_type >> true
; and -
$S_{r-1}$is_convertible_v <
is, tuple < index_type , index_type >> true
;
then
.
(7.5) Else, if
-
isdecltype ( src ) :: layout_type
for somelayout_left_padded < padding_stride >
;size_t padding_stride -
equals zero or one; andExtents :: rank () -
if
equals one, then $S_0$ isExtents :: rank ()
orfull_extent_t
$S_0$is_convertible_v <
is, tuple < index_type , index_type >> true
;
then,
.
(7.6) Else, if
-
isdecltype ( src ) :: layout_type
for somelayout_left_padded < padding_stride >
;size_t padding_stride -
is greater than one;Extents :: rank () -
all the $S_k$ except for $S_0$ and $S_1$ are
;full_extent_t -
$S_0$is_convertible_v <
is, tuple < index_type , index_type >> true
; and -
$S_1$is_convertible_v <
is, tuple < index_type , index_type >> true
;
then
;
(7.7) Else, if
-
isdecltype ( src ) :: layout_type
for somelayout_right_padded < padding_stride >
;size_t padding_stride -
equals zero or one; andExtents :: rank () -
if
equals one, then $S_0$ isExtents :: rank ()
orfull_extent_t
$S_0$is_convertible_v <
is, tuple < index_type , index_type >> true
;
then,
.
(7.8) Else, if
-
isdecltype ( src ) :: layout_type
for somelayout_right_padded < padding_stride >
;size_t padding_stride -
is greater than one;Extents :: rank () -
all the $S_k$ except for the two rightmost $S_{r-2}$ and $S_{r-1}$ are
;full_extent_t -
$S_{r-2}$is_convertible_v <
is, tuple < index_type , index_type >> true
; and -
$S_{r-1}$is_convertible_v <
is, tuple < index_type , index_type >> true
;
then,
then
;
(7.9) Otherwise,
;
4.4. Layout specializations of submdspan_offset
[mdspan.submdspan.offset]
At the top of Section � [mdspan.submdspan.offset] ("Layout specializations of
"), before paragraph 1, add the following to the end of the synopsis of specializations. (Note that all the specializations of
submdspan_offset share the same wording.)
submdspan_offset
template < class Extents , std :: size_t padding_stride , class ... SliceSpecifiers > constexpr size_t submdspan_offset ( const layout_left_padded < padding_stride >:: template mapping < Extents >& src , SliceSpecifiers ... slices ); template < class Extents , std :: size_t padding_stride , class ... SliceSpecifiers > constexpr size_t submdspan_offset ( const layout_right_padded < padding_stride >:: template mapping < Extents >& src , SliceSpecifiers ... slices );