This page is a snapshot from the LWG issues list, see the Library Active Issues List for more information and the meaning of New status.

4632. submdspan_mapping of rank-one padded layouts ignores the stride of an extent_slice, and the sliceable postcondition does not mention strides

Section: 23.7.3.7.7.6 [mdspan.sub.map.leftpad], 23.7.3.7.7.7 [mdspan.sub.map.rightpad], 23.7.3.7.7.1 [mdspan.sub.map.sliceable] Status: New Submitter: Andrii Chornyi Opened: 2026-10-17 Last modified: 2026-10-31

Priority: Not Prioritized

View all issues with New status.

Discussion:

23.7.3.7.7.6 [mdspan.sub.map.leftpad] (and its mirror 23.7.3.7.7.7 [mdspan.sub.map.rightpad]) specify the result of layout_left_padded::mapping<Extents>::submdspan-mapping-impl(slices...). Bullet (1.2) of the Returns element reads:

otherwise, submdspan_mapping_result{layout_left::mapping(sub_ext), offset}, if rank_== 1 is true or SubExtents::rank() == 0 is true;

The first part of the condition applies to every slice of a rank-one mapping, including a strided one. For rank_ == 1 and the slice extent_slice{0, cw<4>, cw<2>} the result has SubExtents::rank() == 1 with extent 2, and layout_left::mapping(sub_ext) has stride 1. The returned mapping therefore addresses the first two elements 0, 1 (plus offset) instead of elements 0, 2.

The general rule of 23.7.3.7.7.2 [mdspan.sub.map.common] gives the intended meaning: for a slice s that is an extent_slice with s.extent > 1, the stride of the corresponding sub-dimension is stride(k) * s.stride (the list that defines sub_strides), so the strided slice yields a layout_stride mapping with stride 2. The layout_left, layout_right and layout_stride specializations (23.7.3.7.7.3 [mdspan.sub.map.left], 23.7.3.7.7.4 [mdspan.sub.map.right], 23.7.3.7.7.5 [mdspan.sub.map.stride]) are unaffected; only the padded specializations contain the rank_ == 1 shortcut. Unit-stride slices are unaffected.

The generic requirement that is meant to catch this does not do so: 23.7.3.7.7.1 [mdspan.sub.map.sliceable] Returns (5.2) requires

smr.mapping(i...) + smr.offset == m(j), where j...[ρ] is equal to the sum of the lower bound of the submdspan slice range ... and zero if the type of valid_slices...[ρ] is a collapsing slice type, i...[MAP_RANK(valid_slices,ρ)] otherwise.

The index i is added to the lower bound without being multiplied by the slice stride. Read literally, the (stride-1) layout_left::mapping above satisfies this postcondition, while a correct layout_stride result for the same slice (the result that 23.7.3.7.7.2 [mdspan.sub.map.common] itself prescribes for the default mappings) does not. The two defects hide each other: the postcondition can neither detect the wrong rank-one result nor accept the right one.

Reproducer: layout_left_padded<dynamic_extent>::mapping<extents<int, 8>> with submdspan_mapping(m, extent_slice{0, cw<4>, cw<2>}): smr.mapping(1) + smr.offset is 1, m(2) is the element the slice selects as its second element.

Proposed resolution:

This wording is relative to N5054.

  1. Modify 23.7.3.7.7.6 [mdspan.sub.map.leftpad] as indicated:

    [Drafting note: The case rank_ == 1 with a unit-stride slice and SubExtents::rank() == 1 is covered by the third bullet (SubExtents::rank() == 1 and SliceSpecifiers...[0] is a unit-stride slice type, same result); a strided slice falls to the last bullet and yields the layout_stride mapping.]

    template<class Extents>
    template<class... SliceSpecifiers>
    constexpr auto layout_left_padded::mapping<Extents>::submdspan-mapping-impl(
    SliceSpecifiers... slices) const -> see below;
    

    -1- Returns:

    1. (1.1) — submdspan_mapping_result{*this, 0}, if Extents::rank() == 0 is true;

    2. (1.2) — otherwise, submdspan_mapping_result{layout_left::mapping(sub_ext), offset}, if rank_ == 1 is true or SubExtents::rank() == 0 is true;

  2. Modify 23.7.3.7.7.7 [mdspan.sub.map.rightpad] as indicated:

    template<class Extents>
    template<class... SliceSpecifiers>
    constexpr auto layout_right_padded::mapping<Extents>::submdspan-mapping-impl(
      SliceSpecifiers... slices) const -> see below;
    

    -1- Returns:

    1. (1.1) — submdspan_mapping_result{*this, 0}, if rank_ == 0 is true;

    2. (1.2) — otherwise, submdspan_mapping_result{layout_right::mapping(sub_ext), offset}, if rank_ == 1 is true or SubExtents::rank() == 0 is true;

  3. Modify 23.7.3.7.7.1 [mdspan.sub.map.sliceable] as indicated:

    -3- A type M meets the sliceable layout mapping requirements if

    […]

    -4- Result: A type SMR that is a specialization of type submdspan_mapping_result<SM> for some type SM such that

    […]

    -5- Returns: An object smr of type SMR such that

    1. (5.1) — smr.mapping.extents() == subextents(m.extents(), valid_slices...) is true; and

    2. (5.2) — for each integer pack i which is a multidimensional index in smr.mapping.extents(), smr.mapping(i...) + smr.offset == m(j) is true, where j is an integer pack such that

      1. (5.2.1) — sizeof...(j) is equal to M_rank; and

      2. (5.2.2) — for each rank index ρ of m.extents(), j...[ρ] is equal to the sum of

        1. (5.2.2.1) — the lower bound of the submdspan slice range of valid_slices...[ρ] for extent ρ of m.extents(), and

        2. (5.2.2.2) — zero if the type of valid_slices...[ρ] is a collapsing slice type, i...[MAP_RANK(valid_slices,ρ)] otherwise, the product of i...[MAP_RANK(valid_slices,ρ)] and s.stride if the type of valid_slices...[ρ] is a specialization of extent_slice and s.extent > 1 is true, where s is valid_slices...[ρ], and i...[MAP_RANK(valid_slices,ρ)] otherwise.