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, 1 + offset + (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

  • Original submdspan paper 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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions