| Document number: | P3982R0 | |
|---|---|---|
| Date: | 2026-01-30 | |
| Audience: | Library Evolution Working Group | |
| Reply-to: | Tomasz Kamiński <tomaszkam at gmail dot com> | |
| Mark Hoemmen <mark dot hoemmen at gmail dot com> |
strided_slice::extent for C++26Addresses PL007: Define the extent member of the strided_slice
This paper proposes three changes:
strided_slice to extent_stride and
adjust the meaning of its extent member, to designate the desired
number of elements in the range produced by submdspan.
This change would be breaking after C++26 is shipped.range_slice slice type,
that expresses the (first, last, stride) interface provided for range
slicing in other programming languages.
This is an extension that can be added in a later Standard.extent_stride)
that can be decomposed into three values (including tuple and
range_slice), are treated as (first, last, stride).
This is another extension that can be added on top of the previous extension
in a later Standard.| Before | After |
|---|---|
std::strided_slice{0, 2, 3};
|
std::extent_slice{0, 1, 3};
std::range_slice{0, 2, 3};
std::tuple{0, 2, 3};
|
std::strided_slice{2, 10, 3};
|
std::extent_slice{2, 4, 3};
std::range_slice{2, 12, 3};
std::tuple{2, 12, 3};
|
Initial revision.
For the invocation of in the form smd = submdspan(md, strided_slice{offset, extent, stride}),
there are two ranges of indices of elements to which we refer:
md, that can be accessed by smd; andsmd.Given the above, there are two possible interpretations of the extent for the above example:
1 + (extent - 1) / stride; orextent * stride.In most cases, this two meanings are functionally equivalent and they can be transformed into each other. However, due use of the division in the input span interpretation does not support the following:
stride whose value is zero, that could be used to produce non-unique layouts; orAs strided_slice is used as one of the canonical forms of the slices,
we propose to strided_slice::extent member should represent the output extent.
range_slice and extending decomposable slicesOne argument for using the input span as the value of stride_slice::extent
was consistency with other programming languages' range slicing interface.
However, surveying the slicing interface in common languages shows that they all use first, last
instead of offset, length.
array(first:last) and array(first:last:step)array[first:last] and array[first:last:step]array(first:last) and array(first:step:last)array[first..last], array[first..], array[..last] and array[..].submdspan(md, strided_slice{2, 5, 1}),
should select elements [2, 5), instead of [2, 7) as currently specified.
To provide a interface consistent with existing practice in many languages, we propose
extending the set of accepted slice types to include types that decompose into three values that
are compatible with index type. So in addition to accepting pairs (including two-element
tuple) representing first, last, we propose to
accept a three-element tuple, where the third value is the stride.
Futhermore, we propose to introduce a new vocabulary type for "range" slice:
template<typename FirstType, typename LastType, typename StrideType = constant_wrapper<1zu>>
struct range_slice
{
[[no_unique_addresss]] FirstType first{};
[[no_unique_addresss]] LastType last{};
[[no_unique_addresss]] StrideType stride{};
};
Note that such extension goes directly against the reasoning for the current design of
strided_slice expressed in 2.1.1.2 Strided index range slice specifier
of the
P2630R4: Submdspan,
paper:
We use a struct with named fields instead of a tuple, in order to avoid confusion
with the order of the three values.
While the author agrees that the proposed order may be unintuitive for Matlab users, such confusion can be easily addressed by users defining the following helper locally:
template<typename FirstType, typename StrideType, typename LastType>
constexpr std::range_slice<FirstType, LastType, StrideType>
matlab_slice(FirstType first, StrideType stride, LastType last)
{ return {first, last, stride}; }
Creating a subset of a multidimensional index space (submdspan) requires
the output extent to be known. In the model where user provides an input span,
the output extent computation is performed, and thus duplicated in each layout.
In contrast, with this paper's proposed changes, the members of strided_slice
directly represent values used by submdspan creation. This gives programmers
more direct control over the process. In particular, in cases when the value of output extent
is known, or can be reused between invocations, passing it directly can lead to measurable
speed-up. We illustrate this by including benchmark results below.
submdspan(md, prefix_slice{0, span, stride}) | ||
|---|---|---|
|
Before | After |
stride \ prefix:span |
strided:10 |
extent:1 + 9 / stride |
3 |
3.03 ns | 1.52 ns |
1 |
3.03 ns | 1.51 ns |
std::cw<3> |
1.01 ns | 1.01 ns |
std::cw<1> |
1.02 ns | 1.01 ns |
As previously mentioned, range_slice can be used if passing
an input span is preferred. Results from a benchmark similar to one
used above show no significant performance difference.
submdspan(md, range_slice{0, 10, stride}) | ||
|---|---|---|
stride |
Before | After |
3 |
3.03 ns | 3.03 ns |
1 |
3.03 ns | 1.52 ns |
std::cw<3> |
1.01 ns | 1.01 ns |
std::cw<1> |
1.01 ns | 1.01 ns |
More details about above results may be found here.
As mentioned before, the current input span specification does not give users a way to
select a statically sized subset of elements with dynamic stride. For example,
imagine that we want to select 5 elements, evenly spaced from the mdspan md.
With the proposed change, we can simply express that as:
auto smd = sumdspan(md, strided_slice{cw<0>, cw<5>, md.extents()[0] / 5})
Note that the number of number of elements in the smd is always known
statically, regardless if the source span had static extents.
Using a zero as the value of the stride leads to a non-unique mappings,
because incrementing the index does not change the referenced element. Thus, they are not
accepted by the standard mappings.
However, submdspan can be also used with mdspan with
user-defined mappings that are not required to be unique. Any mapping can
be queried (via is_always_unique or is_unique) for this property.
One could imagine a layout_stride_relaxed1 layout that is equivalent
to layout_strided, except that it does not require that the provided strides
result in a non-unique mapping. In case of mdspan with such mapping, a zero stride
may be used to create a layout that "broadcasts" a single element over the given extent.
auto smd = submdspan(md, strided_slice{3, 5, 0};
For the above example, each of smd[0], smd[1], ..., smd[4] results in a reference
to md[3].
As mentioned before, such a slice specification is not representable currently. While the paper does not lift the current requirements on the stride value being non-zero, it permits zero strides in the future for a subset of mappings.
1 A version of layout_stride_relaxed was
recently proposed for inclusion
in NVIDIA's CCCL library.
strided_sliceAfter expanding the set of accepted slice types, the strided_slice name
does not capture the difference well, as range_slice is also strided. Thus,
we propose to rename the class to extent_slice.
The extent_slice name was selected to focus on the fact that its members
define the size (extent) of the produced (output) multidimensional index space. That is, it directly reflects
the value of smd.extent(k), where smd is the mdspan produced by submdspan,
and k is the index of the extent to which the slice is applied.
We also avoid using words commonly used to refer to the range like "size,"
"length" (as in offset + length), or "span".
The word "size" is particularly overloaded,
for example in the std::ranges::sized_range concept.
This paper proposes three changes:
strided_slice to extent_stride and
adjust the meaning of extent member, to designate the desired
number of elements in the produced range.range_slice,
that expresses the (first, last, stride) interface for range
slicing provided by other programming languages.extent_slice)
that can be decomposed into three values (including tuple and
range_slice) are treated as (first, last, stride).From the above only the first change need to be applied in the C++26 timeframe. The others are extensions that could be applied to future standards.
1. Accept rename 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.
3. Accept any type decomposable into three elements as submdspan
slice type as proposed in P3982R0 to C++26.
stride_slice needs to target C++26strided_slice is one of the canonical slice types that define the
interface between submdspan (and potentially other components providing
such facility) and custom layouts. Thus, it is important that the interface is both mininal
(reducing the burden on layouts implementers) and able to represent a wide range of
input without loss of information. As this document explains, the current specification
of strided_slice fails in both accounts (it incurs cost of division, and cannot
be used for non-unique layouts).
We currently reserve rights to introduce additional canonical stride types.
We could imagine introducing a separate canonical_strided_slice type
in a later standard. However, in contrast to amending strided_slice,
this would essentially duplicate the number of types that layouts would need to handle,
as pre-existing code depending on sliceable layout requirements may still produce
strided_slice objects.
range_slice should target C++26While range_slice could be added later as an extension, the authors strongly
believe that the ergonomics of submdspan would be severly degraded, without
the ability to specify a slice using an input span.
During the work on this paper, one of the authors (Tomasz) made the following mistakes,
when transforming the input span span to an output extent:
span / stride,(span - 1) / stride (missing +1),span equal to zero.This paper only impacts the behavior of the std::submdspan library function
that was introduced in C++26.
Here is a patch series
implementing the proposed wording changes (except the rename) to submdspan in libstdc++.
The proposed wording changes refer to N5032 (C++ Working Draft, 2025-12-15).
Apply following changes to section [mdspan.syn] Header <mdspan> synopsis:
// [mdspan.sub], submdspan creation
template<class OffsetType, class LengthType, class StrideType>
struct strided_slice;
template<class FirstType, class LastType,
class StrideType = std::constant_wrapper<1zu>>
struct range_slice;
template<class LayoutMapping>
struct submdspan_mapping_result;
Apply following changes to section [mdspan.sub.overview] Overview :
-1- The
submdspanfacilities create a newmdspanviewing a subset of elements of an existing inputmdspan. The subset viewed by the created mdspan is determined by theSliceSpecifierarguments.-2- Given a signed or unsigned integer type
IndexType, a typeSis a submdspan slice type forIndexTypeif at least one of the following holds:
- -2.1-
is_convertible_v<S, full_extent_t>istrue;- -2.2-
is_convertible_v<S, IndexType>istrue;- -2.3-
Sa specialization ofstrided_sliceandis_convertible_v<X, IndexType>istrueforXdenotingS::offset_type,S::extent_type, andS::stride_type;or- -2.4- all of the following hold:
- -2.4.1- the declaration
auto [...ls] = std::move(s);is well-formed for some objectsof typeS,- -2.4.2-
sizeof...(ls)is equal either to2or3, and- -2.4.3-
(is_convertible_v<decltype(std::move(ls)), IndexType> && ...)istrue.-3- Given a signed or unsigned integer type
IndexType, a typeSis a canonicalsubmdspanindex type forIndexTypeifSis eitherIndexTypeorconstant_wrapper<v>for some valuevof typeIndexType, such thatvis greater than or equal to zero.-4- Given a signed or unsigned integer type
IndexType, a typeSis a canonicalsubmdspanslice type forIndexTypeif exactly one of the following istrue:
- -4.1-
Sisfull_extent_t;- -4.2-
Sis a canonical submdspan index type forIndexType; or- -4.3-
Sa specialization ofstrided_slicewhere all of the following hold:
- -4.3.1-
S::offset_type,S::extent_type, andS::stride_typeare all canonical submdspan index types forIndexType; and- -4.2.2- if
S::stride_typeandS::extent_typeare both specializations ofconstant_wrapper, thenS::stride_type::valueis greater than zero.-5- A type
Sis a collapsing slice type if […]-6- A type
Sis a unit-stride slice type if […]-7- Given an object
eof typeEthat is a specialization ofextents, and an objectsof typeSthat is a canonical submdspan slice type forE::index_type, thesubmdspanslice range ofsfor the kth extent ofeis:
- -7.1-
[0, e.extent(k)), ifSisfull_extent_t;- -7.?-
[E::index_type(s.offset), E::index_type(s.offset)), ifSis a specialization ofstrided_sliceandE::index_type(s.extent)is zero; otherwise- -7.2-
[E::index_type(s.offset), E::index_type(s.offset + 1 + (s.extent - 1) * s.stride)), ifSis a specialization ofstrided_slice; otherwise- -7.3-
[E::index_type(s), E::index_type(s)+1)-8- Given a type
Ethat is a specialization ofextents, a typeSis a validsubmdspanslice type for the kth extent ofEifSis a canonical slice type forE::index_type, and forxequal toE::static_extent(k), eitherxis equal todynamic_extent; or
- -8.1- if
Sis a specialization ofstrided_slice:
- -8.1.1- if
S::offset_typeis a specialization ofconstant_wrapper, thenS::offset_type::valueis less than or equal tox;- -8.1.2- if
S::is a specialization ofoffset_typeextent_typeconstant_wrapperthenS::extent_type::valueis less than or equal tox;- -8.1.3- if
wherebothS::offset_typeandS::extent_typeareis a specializationsofconstant_wrapperandS::extent_type::valueis greater then zero then,
- -8.1.3.2- if
S::offset_typespecialization ofconstant_wrapper, thenS::offset_type::value + S::extent_type::valueis less than or equal tox,- -8.1.3.2- if
S::stride_typeis specialization ofconstant_wrapper, thenS::stride_type::valueis greater than zero andris less thanx, and- -8.1.3.3- if both
S::offset_typeandS::stride_typeare specializations ofconstant_wrapper, thenS::offset_type::value +is less thanS::extent_type::valueror equal tox;,ris1 + (S::extent_type::value - 1) * S::stride_type::value;- -8.2- if
Sis a specialization ofconstant_wrapper, thenS::valueis less thanx-9- Given an object
eof typeEthat is a specialization ofextentsand an objectsof typeS,sis a validsubmdspanslice for the kth extent ofeif:
- -9.1-
Sis a validsubmdspanslice type for kth extent ofE;- -9.2- the kth interval of
econtains thesubmdspanslice range ofsfor the kth extent ofe; and- -9.3- if
Sa specialization ofstrided_slicethen:
- -9.3.1-
s.extentis greater than or equal to zero, and- -9.3.2- either
s.extentequals zero ors.strideis greater than zero.
Apply following changes to section [mdspan.sub.strided.slice] strided_slice:
23.7.3.7.2
Range slices [mdspan.sub.strided.slice]strided_slice
- -1-
strided_sliceandrange_slicerepresentsa set of extent regularly spaced integer indices. The indices start atoffsetandfirstrespectively, and increase by increments ofstride.namespace std { template<class OffsetType, class ExtentType, class StrideType> struct strided_slice { using offset_type = OffsetType; using extent_type = ExtentType; using stride_type = StrideType; [[no_unique_address]] offset_type offset{}; [[no_unique_address]] extent_type extent{}; [[no_unique_address]] stride_type stride{}; }; template<class FirstType, class LastType, class StrideType = std::constant_wrapper<1zu>> struct range_slice { [[no_unique_address]] FirstType first{}; [[no_unique_address]] LastType last{}; [[no_unique_address]] StrideType stride{}; }; }
- -2-
strided_sliceandrange_slicehashave the data members and special members specified above.It hasThey have no base classes or members other than those specified.- -3- Mandates:
OffsetType,ExtentType,FirstType,LastType, andStrideTypeare signed or unsigned integer types, or modelintegral-constant-like.- [ Note: Both
strided_slice{ .offset = 1, .extent =and104, .stride = 3}range_slice{ .first = 1, .last = 11, .stride = 3}indicates the indices1,4,7, and10. Indices are selected from the half-open interval[1, 1 + 10). — end note]
Apply following changes to section [mdspan.sub.helpers] Exposition-only helpers:
templatelt&;class IndexType, class S> constexpr auto canonical-index(S s);
- -3- Mandates:
- […];
- -4- Preconditions:
- […];
- -5- Effects:
- […];
template<class IndexType, class OffsetType, class SpanType, class... StrideTypes> constexpr auto canonical-range-slice(OffsetType offset, SpanType span, StrideTypes... strides);
- -?- Let:
StrideTypebeconstant_wrapper<IndexType(1)>ifsizeof...(StrideTypes) == 0orSpanTypedenotesconstant_wrapper<IndexType(0)>, andStridesTypes...[0]otherwise;stridebe
StrideType()ifStrideTypeis specialization ofconstant_wrapper, otherwiseIndexType(1)ifspan == 0istrue, otherwisestrides...[0];extent-valuebe1 + (span - 1) / strideifspan != 0istrue, and0otherwise;extentbecw<IndexType(extent-value)>if bothSpanTypeandStrideTypeare specializations ofconstant_wrapper, andIndexType(extent-value)otherwise.- -?- Mandates:
sizeof..(StrideTypes) <= 1istrue, and- if
StrideTypeis specialization ofconstant_wrapper, thenStrideType::value > 0istrue.- -?- Preconditions:
IndexType(stride) > 0istrue.- -?- Returns:
strided_slice{ .offset = first, .extent = extent, .stride = stride };template<class IndexType, class S> constexpr auto canonical-slice(S s);
- -6- Mandates:
Sis asubmdspanslice type forIndexTye.- -7- Effects:
- Equivalent to:
if constexpr (is_convertible_v<S, full_extent_t>) { return static_cast<full_extent_t>(std::move(s)); } else if constexpr (is_convertible_v<S, IndexType>) { return canonical-index<IndexType>(std::move(s)); } else if constexpr (is-strided-slice<S>) { return strided_slice{ .offset = canonical-index<IndexType>(std::move(s.extent)), .extent = canonical-index<IndexType>(std::move(s.offset)), .stride = canonical-index<IndexType>(std::move(s.stride)) }auto c_extent = canonical-index<IndexType>(std::move(s.extent)); auto c_offset = canonical-index<IndexType>(std::move(s.offset)); if constexpr (is_same_v<decltype(c_extent), constant_wrapper<IndexType(0)>>) { return strided_slice{ .offset = c_offset, .extent = c_extent, .stride = cw<IndexType(1)> }; } else { return strided_slice{ .offset = c_offset, .extent = c_extent, .stride = canonical-index<IndexType>(std::move(s.stride)) }; }} else { auto [s_first, s_last, ...s_stride] = std::move(s); auto c_first = canonical-index<IndexType>(std::move(s_first)); auto c_last = canonical-index<IndexType>(std::move(s_last));return strided_slice{ .offset = c_first, .extent = canonical-index<IndexType>(c_last - c_first), .stride = cw<IndexType(1)> };return canonical-slice-range<IndexType>( c_first, canonical-index<IndexType>(c_last - c_first), canonical-index<IndexType>(std::move(s_stride))...); }
Apply following changes to section [mdspan.sub.extents] submdspan_extents function:
template<class IndexType, size_t... Extents, class... SliceSpecifiers> constexpr auto submdspan_extents(const extents<IndexType, Extents...>& src, SliceSpecifiers... raw_slices);
- -1- Let
slicesbe […];- -2- Constraints:
- […];
- -3- Mandates:
- […];
- -4- Preconditions:
- […];
-5- Let
SubExtentsbe a specialization ofextentssuch that:
- -5.1-
SubExtents::rank()equalsMAP_RANK(slices, Extents::rank());and;- -5.2- for each rank index
kofExtentsextents<IndexType, Extents...>such that the type ofslices...[k]is not a collapsing slice type,SubExtents::static_extent(MAP_RANK(slices, k))equals the following, whereΣkdenotes the type ofslices...[k]:
- -5.2.1-
Extents::static_extent(k)ifΣkdenotes thefull_extent_t; otherwise-5.2.2-0, ifΣkis a specialization ofstrided_sliceandΣk::extent_typedenotesconstant_wrapper<IndexType(0)>; otherwise- -5.2.3-
Σk::extent_type::value if1 + ((Σk::extent_type::value - 1) / Σk::stride_type::value)Σkis a specialization ofstrided_slicewhoseextent_typeanddenotes specializationstride_typesofconstant_wrapper;- -5.2.4- otherwise,
dynamic_extent.- -6- Returns:
A value
extof typeSubExtentssuch that for each indexkofextents<IndexType, Extents...>, where the type ofslices...[k]is not a collapsing slice type,ext.extent(MAP_RANK(slices, k))equals the following whereσkdenotesslices...[k]:
- -6.1-
σk.extentif the type of σk is specialization of== 0 ? 0 : 1 + (σk.extent - 1) / σk.stridestrided_slice,- -6.2- otherwise,
U−L, where[L, U)is thesubmdspanslice range ofσkfor thekthextent ofsrc.
Apply following changes to section [mdspan.sub.map.common] Common:
-6- Let
sub_stridesbe anarray<SubExtents::index_type, SubExtents::rank()>such that for each rank indexkofextents()for which the type ofslices...[k]is not a collapsing slice type,sub_strides[MAP_RANK(slices,k)]equals:
- -6.1-
stride(k) * s.strideif type ofsis a a specialization ofstrided_sliceands.stride < s.extents.extent > 1istrue, wheresisslices...[k];- -6.2- otherwise,
stride(k).
Replace all occurrences of strided_slice and is-strided-slice
in [mdspan.sub] with extent_slice and is-extent-slice respectively.
Update the value of the __cpp_lib_submdspan in [version.syn]
Header <version> synopsis to reflect the date of approval of this proposal.
Christian Trott offered many useful suggestions and corrections to the proposal.