Skip to content

LEWG 2026/02/10: PL-007, P3982R0 #448

@mhoemmen

Description

@mhoemmen

PL-007 23.7.3.7 [mdspan.sub] Define the extent member of the strided_slice

NB comment: https://github.com/cplusplus/nbballot/issues/816

Paper: https://isocpp.org/files/papers/P3982R0.html

Make strided_slice:::extent define the output extent, not the input span

2 ranges of indices in submdspan

  1. input span: range of indices into input, that can be accessed by output
  2. output extent: size of the range of valid indices for output

2 possible choices of meaning for strided_slice's extent

  1. input span $[$ offset, offset + extent $)$, thus output extent is 1 + (extent - 1) / stride
    • STATUS QUO
  2. output extent, thus input span is $[$ offset, offset + 1 + (extent - 1)*stride $)$
    • P3982

Example: status quo vs. P3982R0

Example: strided_slice{.offset = 1, .extent = 10, .stride = 3}.

  • Status quo
    • View input indices 1, 4, 7, and 10
    • Input span: $[1, 1 + 10) = [1, 11)$
    • Output extent: offset + (extent - 1) / stride = 1 + (10 - 1) / 3 = 4
      • NOT same as input .extent 10
Image
  • P3982R0
    • View input indices 1, 4, 7, 10, 13, 16, 19, 22, 25, 28
    • Input span: $[$ offset, offset + 1 + (extent - 1)*stride $)$ = $[1, 29)$
    • Output extent: 10
      • Same as input .extent
Image

Status quo aimed for minimal change from first : last : stride

  • P2630 lacked P3663's notion of "canonical slice types" vs. all slice types
  • Thus, P2630 authors defined slice types based on familiarity
  • Most familiar syntax: Fortran's and Python's first : last : stride
  • But first : last : stride can't represent (dynamic offset, static extent, static stride)
    • Key case for performance, e.g., unrolled loop
  • Status quo in P2630 made the minimal-distance-from-familiarity change to include this case:
    • Replace last with extent = last - first
    • Rename first to offset

Why change status quo?

NB comment points out 2 reasons:

  1. Permit zero strides in the future (for "broadcasting" layouts)
  2. Enable representing (static extent, dynamic stride)

P3982R0 points out 2 more:

  1. Avoid integer divisions
    • Performance
    • Remove undefined behavior (division by zero stride)
  2. Less confusing (input "extent" is not output extent; I have to look this up every time)

Another reason: strided_slice is a canonical slice type

  • Canonical slice types define interface between submdspan and a layout mapping's submdspan_mapping
  • submdspan function body looks like this:
auto [...canonical_slices] =
    submdspan_canonicalize_slices(src.extents(), user_slices...);
auto sub_map_result =
    submdspan_mapping(src.mapping(), canonical_slices...);
return mdspan(
    src.accessor().offset(src.data_handle(), sub_map_result.offset),
    sub_map_result.mapping,
    typename AccessorPolicy::offset_policy(src.accessor()));
  • Choose canonical types to express the biggest possible set of slices
  • We can always add new user-facing slices to improve familiarity (see range_slice below)

Why do we need to change it for C++26?

Changing the meaning of strided_slice::extent later would silently break the meaning of submdspan.

(It would be an ABI break. You don't like those, right?)

Also for C++26: Rename strided_slice to extent_slice

  • Paper proposes a new non-canonical slice type range_slice to cover common use case first : last : stride (Python, Fortran, etc.)

  • Whether that arrives in C++26 or C++29, that would mean there are multiple *_slice types with a stride

  • strided_slice is a canonical slice type; changing name after C++26 would break users' customizations of submdspan_mapping

  • New name: extent_slice, because it has the output extent, not the input span

Additional new features

Paper proposes 2 new features. They could be added for C++26 or in a later Standard.

  1. range_slice{.first = first, .last = last, .stride = stride}

    • Models slicing in other programming languages
    • Calls for renaming strided_slice TO extent_slice NOW, even if we add range_slice later
      • range_slice also has a "stride"
      • So "strided_slice" wouldn't be the only "strided" slice
  2. Interpret any type for which structured binding auto [first, last, stride] = s; is valid as range_slice

    • Only makes sense if we add range_slice as in (1)

Why add range_slice in C++26?

  • Popular programming languages have first : last : stride slices, so users will demand them

    • Fortran: array(first:last), array(first:last:step)
    • Python: array[first:last], array[first:last:step]
    • Matlab: array(first:last), (annoyingly) array(first:step:last)
  • Designated initializers alleviate order concerns

Why add auto [first, last, stride] = s;?

  • Confusion about order (Python vs. Matlab) led the submdspan (P2630) authors not to include this feature, but
    • status quo already interprets auto [first, last] = s; as stride = cw<1zu>
    • so auto [first, last, stride] = s; is less ambiguous, analogous to a default function parameter stride = cw<1zu>
  • Trivial to implement: we already handle auto [...ts] = t; with sizeof...(ts) == 2
  • We consider this optional for C++26

Suggested polls

  1. Accept rename [to extent_slice] and changes to strided_slice class template from P3982R0 to C++26.

  2. Accept the introduction of range_slice class template from P3982R0 to C++26.

  3. Accept any type decomposable into three elements as submdspan slice type as proposed in P3982R0 to C++29.

  4. Accept any type decomposable into three elements as submdspan slice type as proposed in P3982R0 to C++26.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions