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 extents_type extents < index_type , padding_stride > 
- 
       Make converting constructors from layout_ { left , right } _padded :: mapping layout_ { left , right } :: mapping 
- 
       Mandate that layout_ { left , right } _padded :: mapping index_type 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, 
- 
     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 
3.2. Two new mdspan layouts
The two new mdspan layouts 
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 layout_stride constexpr std :: array 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 index_type layout_stride :: mapping 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 (
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 
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, 
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
Users may ask why they can’t convert a more overaligned mapping,
such as 
layout_left_padded < 4 >:: mapping m_orig { extents { 9 , 2 }}; layout_left_padded < 2 >:: mapping m_new ( m_orig ); 
The issue is that 
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, 
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 
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 
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 
The phrase "if the slice arguments permit it" means the following.
3.3.1. layout_left_padded layout_left 
   In what follows, let 
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 true,
and let true.
Let 
Let 
- 
     srm1_val1 - srm1_val0 srm1 tuple < integral_constant < decltype ( W ) :: index_type , srm1_val0 > , integral_constant < decltype ( W ) :: index_type , srm1_val1 >> srm1_val1 srm1_val0 
- 
     dynamic_rank 
Also, 
3.3.2. layout_right_padded layout_right 
   In what follows, let 
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 true,
and let true.
Similarly, let true,
and let 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 ); 
Let true,
and let 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 ); 
Similarly, let true,
and let 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 ); 
Preservation of these layouts under 
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 
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 
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, 
On discovering this, the author of these functions
might be tempted to write a custom layout for "BLAS-compatible" matrices.
However, the 
Alternately, the author of these functions could specialize 
After our proposal, the author can specialize 
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 
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 
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 
First, the goal of 
Second, the 
    Third, the performance benefit of storing 
    
Fourth, a strided mdspan that can represent layouts as general as 
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 
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: true.
Mandates: If
- 
     Extents :: rank () 
- 
     Extents :: static_extent ( 0 ) dynamic_extent 
- 
     OtherExtents :: static_extent ( 0 ) dynamic_extent 
- 
     other_padding_stride dynamic_extent 
then the least multiple of 
- 
     is representable as a value of type Extents :: index_type 
- 
     equals Extents :: static_extent ( 0 ) 
Preconditions:
- 
     If OtherExtents :: rank () other . stride ( 1 ) index_type 
- 
     if extents_type :: rank () > 0 true, then for all $r$ in the range [0,extents_type :: rank () other . stride ( ) extents (). fwd - prod - of - extents ( ) 
- 
     other . required_span_size () index_type 
Effects: Direct-non-list-initializes 
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: true.
Mandates: If
- 
     Extents :: rank () 
- 
     Extents :: static_extent ( Extents :: rank () - 1 ) dynamic_extent 
- 
     OtherExtents :: static_extent ( Extents :: rank () - 1 ) dynamic_extent 
- 
     other_padding_stride dynamic_extent 
then the least multiple of 
- 
     is representable as a value of type Extents :: index_type 
- 
     equals Extents :: static_extent ( Extents :: rank () - 1 ) 
Preconditions:
- 
     If OtherExtents :: rank () other . stride ( OtherExtents :: rank () - 2 ) index_type 
- 
     if extents_type :: rank () > 0 true, then for all $r$ in the range [0,extents_type :: rank () other . stride ( ) extents (). rev - prod - of - extents ( ) 
- 
     other . required_span_size () index_type 
Effects: Direct-non-list-initializes 
After the end of Section � [mdspan.layout.stride], add the following:
4.1. Class template layout_left_padded :: mapping 
   
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 
- 
     If extents_type :: rank () 
- 
     else, the parameter pack size_t ( 1 ) size_t ( 2 ) extents_type :: rank () - 1 
Mandates: If
- 
     extents_type :: rank () 
- 
     padding_stride dynamic_extent 
- 
     extents_type :: static_extent ( 0 ) dynamic_extent 
then the least multiple of 
static constexpr size_t < it > actual - padding - stride </ it > = /* see-below */ ; // exposition only 
- 
     If extents_type :: rank () padding_stride 
- 
     Else, if - 
       padding_stride dynamic_extent 
- 
       extents_type :: static_extent ( 0 ) dynamic_extent 
 then the size_t padding_stride extents_type :: static_extent ( 0 ) 
- 
       
- 
     Otherwise, dynamic_extent 
using < it > inner - extents - type </ it > = /* see-below */ ; // exposition only 
- 
     If extents_type :: rank () inner - extents - type extents_type 
- 
     Otherwise, inner - 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 extents_type :: rank () unpadded - extent - type extents < index_type > 
- 
     Otherwise, unpadded - extent - type extents < index_type , extents_type :: static_extent ( 0 ) > 
constexpr mapping ( const extents_type & ext ); 
Preconditions: If 
Effects:
- 
     Direct-non-list-initializes inner - mapping_ - 
       ext extents_type :: rank () 
- 
       ext . extent ( 0 ), ext . extent ( P_left )... padding_stride dynamic_extent 
- 
       S_left , ext . extent ( P_left )... S_left padding_stride ext . extent ( 0 ) 
 
- 
       
- 
     if extents_type :: rank () unpadded - extent_ unpadded - extent_ ext . extent ( 0 ) 
template < class Size > constexpr mapping ( const extents_type & ext , Size padding_value ); 
Constraints:
- 
     is_convertible_v < Size , index_type > true, and
- 
     is_nothrow_constructible_v < index_type , Size > true.
Preconditions:
- 
     If padding_stride dynamic_extent - 
       padding_value index_type 
- 
       the result of converting padding_value index_type padding_stride 
 
- 
       
- 
     If extents_type :: rank () padding_value ext . extent ( 0 ) index_type 
Effects:
- 
     Direct-non-list-initializes inner - mapping_ - 
       ext extents_type :: rank () 
- 
       S_left , ext . extent ( P_left )... S_left padding_value ext . extent ( 0 ) 
 
- 
       
- 
     if extents_type :: rank () unpadded - extent_ unpadded - 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: true.
Mandates: true.
Preconditions:
- 
     If extents_type :: rank () > 1 trueandpadding_stride dynamic_extent other . stride ( 1 ) padding_stride extents_type :: index - cast ( other . extent ( 0 )) 
- 
     other . required_span_size () index_type 
Effects:
- 
     Direct-non-list-initializes inner - mapping_ - 
       other . extents () extents_type :: rank () 
- 
       other . stride ( 1 ), other . extents (). extent ( P_left )... 
 
- 
       
- 
     if extents_type :: rank () unpadded - extent_ unpadded - extent_ other . extents (). extent ( 0 ) 
Remarks: The expression inside 
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:
- 
     extents_type :: rank () 
- 
     is_constructible_v < extents_type , OtherExtents > true.
Precondition: 
Effects:
- 
     Direct-non-list-initializes inner - mapping_ other . extents () 
- 
     if extents_type :: rank () unpadded - extent_ unpadded - 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 extents_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:
- 
     sizeof ...( Indices ) == Extents :: rank () true,
- 
     ( std :: is_convertible_v < Indices , index_type > && ...) true, and
- 
     ( std :: is_nothrow_constructible < index_type , Indices > && ...) true.
    Precondition: 
Effects: Let P be a parameter pack such that true.
Equivalent to: 
[Note: Effects are also equivalent to
return  inner - mapping_ ( idxs ...); static constexpr bool is_always_exhaustive () noexcept ; 
Returns:
- 
     If extents_type :: rank () true;
- 
     else, if neither < it > inner - mapping - type </ it >:: static_extent ( 0 ) extents_type :: static_extent ( 0 ) dynamic_extent < it > inner - mapping - type </ it >:: static_extent ( 0 ) == extents_type :: static_extent ( 0 ) 
- 
     otherwise, false.
constexpr bool is_exhaustive () const noexcept ; 
Returns:
- 
     If extents_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 
   
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 
- 
     If extents_type :: rank () 
- 
     else, the parameter pack size_t ( 0 ) size_t ( 1 ) extents_type :: rank () - 2 
Mandates: If
- 
     extents_type :: rank () 
- 
     padding_stride dynamic_extent 
- 
     extents_type :: static_extent ( extents_type :: rank () - 1 ) dynamic_extent 
then the least multiple of 
static constexpr size_t < it > actual - padding - stride </ it > = /* see-below */ ; // exposition only 
- 
     If extents_type :: rank () padding_stride 
- 
     Else, if - 
       padding_stride dynamic_extent 
- 
       extents_type :: static_extent ( 0 ) dynamic_extent 
 then the size_t padding_stride extents_type :: static_extent ( 0 ) 
- 
       
- 
     Otherwise, dynamic_extent 
using < it > inner - extents - type </ it > = /* see-below */ ; // exposition only 
- 
     If extents_type :: rank () inner - extents - type extents_type 
- 
     Otherwise, inner - 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 extents_type :: rank () unpadded - extent - type extents < index_type > 
- 
     Otherwise, unpadded - extent - type extents < index_type , extents_type :: static_extent ( Extents :: rank () - 1 ) > 
constexpr mapping ( const extents_type & ext ); 
Preconditions: If 
Effects:
- 
     Direct-non-list-initializes inner - mapping_ - 
       ext extents_type :: rank () 
- 
       ext . extent ( P_right )..., ext . extent ( extents_type :: rank () - 1 ) padding_stride dynamic_extent 
- 
       ext . extent ( P_right )..., S_right S_right padding_stride ext . extent ( extents_type :: rank () - 1 ) 
 
- 
       
- 
     if extents_type :: rank () unpadded - extent_ unpadded - extent_ ext . extent ( extents_type :: rank () - 1 ) 
template < class Size > constexpr mapping ( const extents_type & ext , Size padding_value ); 
Constraints:
- 
     is_convertible_v < Size , index_type > true, and
- 
     is_nothrow_constructible_v < index_type , Size > true.
Preconditions:
- 
     If padding_stride dynamic_extent - 
       padding_value index_type 
- 
       the result of converting padding_value index_type padding_stride 
 
- 
       
- 
     If extents_type :: rank () padding_value ext . extent ( extents_type :: rank () - 1 ) index_type 
Effects:
- 
     Direct-non-list-initializes inner - mapping_ - 
       ext extents_type :: rank () 
- 
       ext . extent ( P_right )..., S_right S_right padding_value ext . extent ( extents_type :: rank () - 1 ) 
 
- 
       
- 
     if extents_type :: rank () unpadded - extent_ unpadded - 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: true.
Mandates: true.
Preconditions:
- 
     If extents_type :: rank () > 1 trueandpadding_stride dynamic_extent other . stride ( extents_type :: rank () - 2 ) padding_stride extents_type :: index - cast ( other . extent ( OtherExtents :: rank () - 1 )) 
- 
     other . required_span_size () index_type 
Effects:
- 
     Direct-non-list-initializes inner - mapping_ - 
       other . extents () extents_type :: rank () 
- 
       other . extents (). extent ( P_right )..., other . stride ( extents_type :: rank () - 2 ) 
 
- 
       
- 
     if extents_type :: rank () unpadded - extent_ unpadded - extent_ other . extents (). extent ( extents_type :: rank () - 1 ) 
Remarks: The expression inside 
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:
- 
     extents_type :: rank () 
- 
     is_constructible_v < extents_type , OtherExtents > true.
Preconditions: 
Effects:
- 
     Direct-non-list-initializes inner - mapping_ other . extents () 
- 
     if extents_type :: rank () unpadded - extent_ unpadded - 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 extents_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:
- 
     sizeof ...( Indices ) == Extents :: rank () true,
- 
     ( std :: is_convertible_v < Indices , index_type > && ...) true, and
- 
     ( std :: is_nothrow_constructible < index_type , Indices > && ...) true.
Precondition: 
Effects: Let true.
Equivalent to: 
[Note: Effects are also equivalent to
return  inner - mapping_ ( idxs ...); static constexpr bool is_always_exhaustive () noexcept ; 
Returns:
- 
     If extents_type :: rank () true;
- 
     else, if neither < it > inner - mapping - type </ it >:: static_extent ( extents_type :: rank () - 1 ) extents_type :: static_extent ( extents_type :: rank () - 1 ) dynamic_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 extents_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 
   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
- 
     decltype ( src ) :: layout_type layout_left 
- 
     Extents :: rank () 
- 
     all the $S_k$ except for $S_0$ and $S_1$ are full_extent_t 
- 
     is_convertible_v < , tuple < index_type , index_type >> true; and
- 
     is_convertible_v < , tuple < index_type , index_type >> true;
then 
(7.4) Else, if
- 
     decltype ( src ) :: layout_type layout_right 
- 
     Extents :: rank () 
- 
     all the $S_k$ except for the two rightmost $S_{r-2}$ and $S_{r-1}$ are full_extent_t 
- 
     is_convertible_v < , tuple < index_type , index_type >> true; and
- 
     is_convertible_v < , tuple < index_type , index_type >> true;
then 
(7.5) Else, if
- 
     decltype ( src ) :: layout_type layout_left_padded < padding_stride > size_t padding_stride 
- 
     Extents :: rank () 
- 
     if Extents :: rank () full_extent_t is_convertible_v < , tuple < index_type , index_type >> true;
then, 
(7.6) Else, if
- 
     decltype ( src ) :: layout_type layout_left_padded < padding_stride > size_t padding_stride 
- 
     Extents :: rank () 
- 
     all the $S_k$ except for $S_0$ and $S_1$ are full_extent_t 
- 
     is_convertible_v < , tuple < index_type , index_type >> true; and
- 
     is_convertible_v < , tuple < index_type , index_type >> true;
then 
(7.7) Else, if
- 
     decltype ( src ) :: layout_type layout_right_padded < padding_stride > size_t padding_stride 
- 
     Extents :: rank () 
- 
     if Extents :: rank () full_extent_t is_convertible_v < , tuple < index_type , index_type >> true;
then, 
(7.8) Else, if
- 
     decltype ( src ) :: layout_type layout_right_padded < padding_stride > size_t padding_stride 
- 
     Extents :: rank () 
- 
     all the $S_k$ except for the two rightmost $S_{r-2}$ and $S_{r-1}$ are full_extent_t 
- 
     is_convertible_v < , tuple < index_type , index_type >> true; and
- 
     is_convertible_v < , tuple < index_type , index_type >> true;
then,
then 
(7.9) Otherwise, 
4.4. Layout specializations of 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 );