Title
`submdspan_mapping` of rank-one padded layouts ignores the stride of an `extent_slice`, and the sliceable postcondition does not mention strides
Status
new
Section
[mdspan.sub.map.leftpad] [mdspan.sub.map.rightpad] [mdspan.sub.map.sliceable]
Submitter
Andrii Chornyi

Created on 2026-10-17.00:00:00 last changed 1 week ago

Messages

Date: 2026-10-31.13:50:57

Proposed resolution:

This wording is relative to N5054.

  1. Modify [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 [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 [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.

Date: 2026-10-17.00:00:00

[mdspan.sub.map.leftpad] (and its mirror [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 [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 ([mdspan.sub.map.left], [mdspan.sub.map.right], [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: [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 [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.

History
Date User Action Args
2026-10-31 13:50:57adminsetmessages: + msg16635
2026-10-17 00:00:00admincreate