1. Authors and contributors
1.1. Authors
- 
     
Mark Hoemmen (mhoemmen@nvidia.com) (NVIDIA)
 - 
     
Christian Trott (crtrott@sandia.gov) (Sandia National Laboratories)
 - 
     
Damien Lebrun-Grandie (lebrungrandt@ornl.gov) (Oak Ridge National Laboratory)
 - 
     
Malte Förster (mfoerster@nvidia.com) (NVIDIA)
 - 
     
Jiaming Yuan (jiamingy@nvidia.com) (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 fromlayout_left :: mapping and the constructor converting fromlayout_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 fromlayout_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 fromlayout_right :: mapping and the constructor converting fromlayout_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 fromlayout_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 trueanddoes 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 trueanddoes 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 ofsubmdspan_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 );