C++ Logo

std-proposals

Advanced search

Re: [std-proposals] `random_access_iterator_accessor` for `std::mdspan`?

From: Hewill Kang <hewillk_at_[hidden]>
Date: Mon, 6 Apr 2026 13:54:03 +0800
Another interesting point I would like to point out is that the standard
does allow `ranges::to` create `mdspans`, so the following is well-formed (
https://godbolt.org/z/o6ob9Mafq):

  int x[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
  auto dim_1 = x | std::ranges::to<std::mdspan>(1);
  auto dim_2x3 = x | std::ranges::to<std::mdspan>(2, 3);
  auto dim_2x3x4 = x | std::ranges::to<std::mdspan>(2, 3, 4);
Since ranges::to will dispatch the first bullet of [range.utility.conv.to]
<https://eel.is/c++draft/ranges#range.utility.conv.to-2.1.1>.

Hewill Kang <hewillk_at_[hidden]> 於 2026年4月6日週一 下午1:29寫道:

> Using `from_range` might suggest that a container is being constructed
>> "from" a range. That's not what's happening here. The mdspan is a
>> view of the range itself. It's not "from" a range, it IS the range,
>> just a multidimensional view of it.
>
>
> I don't think that, currently, disallowing the creation of a *view* with
> ranges::to is a good direction.
> So I wrote P3544 ranges::to view
> <https://open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3544r0.html> ,
> which is supported by SG9 as far as I know.
>
> Have you considered just adding nonmember functions that create an
>> mdspan with the desired properties, such as the following?
>
>
> I think adding a `from_range` constructor to `mspan` is worthwhile. It
> provides at least two enhancements that I find quite nice:
>
> 1. It can perform compile-time and runtime size validation, which
> pointer-based currently cannot do:
>
> array<int, 12> arr{};
> auto m1 = mdspan(from_range, arr, cw<3>, cw<4>); // ok
> auto m2 = mdspan(from_range, arr, cw<4>, cw<4>); // compile-time reject
>
> vector<float> v(15);
> auto m3 = mdspan(from_range, v, 3, 5); // ok
> auto m4 = mdspan(from_range, v, 4, 5); // runtime assert
>
> This is feasible because for integral-constant-like types of extents, the
> mapping's `required_span_size()` becomes a constant expression.
>
> 2. It integrates well with `ranges::to`, as the latter natively supports
> `from_range_t tag-dispatched` constructors according to
> [range.utility.conv.to]
> <https://eel.is/c++draft/ranges#range.utility.conv.to-2.1.2>.
>
> auto grid_10x10 = views::iota(0) | ranges::to<mdspan>(10, 10);
>
> vector x_coords = {0, 10, 20};
> vector y_coords = {0, 5};
> vector z_coords = {0, 100};
> // Represents a 3D grid of coordinate tuples: (x, y, z)
> auto coord_grid = views::cartesian_product(x_coords, y_coords, z_coords);
> auto points = coord_grid | ranges::to<mdspan>(x_coords.size(),
> y_coords.size(),
> z_coords.size());
>
> // Physical buffer: Data is interleaved or padded (e.g., 128-bit alignment)
> vector<float> hardware_buffer = { /* large padded data */ };
> auto mat_4x4 = hardware_buffer | views::stride(4) | views::as_const | ranges::to<mdspan>(4, 4);
>
> I think this spelling might be more fascinating because we only focus on
> the parameter of *dimension*.
>
> Thanks,
> Hewill
>
> Mark Hoemmen <mark.hoemmen_at_[hidden]> 於 2026年4月6日週一 下午12:28寫道:
>
>> On Sat, Apr 4, 2026 at 10:28 AM Hewill Kang <hewillk_at_[hidden]> wrote:
>> >>
>> >> Currently, the creation of `mdspans` with `iterator_accessor` relies
>> on the following CTAD:
>> >> ```
>> >> template<class MappingType, class AccessorType>
>> >> mdspan(const typename AccessorType::data_handle_type&, const
>> MappingType&,
>> >> const AccessorType&)
>> >> -> mdspan<typename AccessorType::element_type, typename
>> MappingType::extents_type,
>> >> typename MappingType::layout_type, AccessorType>;
>> >> ```
>> >> We must spell with
>> >> ```
>> >> auto _3x3 = std::layout_right::mapping(std::extents(3, 3));
>> >> auto ms = std::mdspan(r.begin(), _3x3,
>> std::iterator_accessor(r.begin()));
>>
>> Not quite. The accessor is stateless in this case.
>>
>> auto ms = std::mdspan(r.begin(), _3x3,
>> std::iterator_accessor<decltype(r.begin())>());
>>
>> Accessors don't see the data handle until first use. (P2897,
>> aligned_accessor, talks a bit about how this relates to the
>> possibility of checking the data handle's preconditions.)
>>
>> > Typing `r.begin()` twice is unsatisfactory to me.
>> > I think we can provide an additional constructor for `mdspan` to
>> indicate that it is constructed from ranges:
>> >
>> > ```
>> > template<ranges::random_access_range Range, class... OIndexTypes>
>> > requires ranges::borrowed_range<Range>
>> > constexpr explicit
>> > mdspan(from_range_t, Range&& r, OIndexTypes... exts) :
>> ptr_(ranges::begin(r)), ...
>> > ```
>> >
>> > Along with the following CTAD:
>> >
>> > ```
>> > template<ranges::random_access_range Range, class... Integrals>
>> > explicit mdspan(from_range_t, Range&&, Integrals...)
>> > -> mdspan<typename
>> iterator_accessor<ranges::iterator_t<Range>>::element_type,
>> > extents<size_t, maybe-static-ext<Integrals>...>,
>> > layout_right,
>> > iterator_accessor<ranges::iterator_t<Range>>>
>> > ```
>> >
>> > This allows the user to spell it out simply with:
>> >
>> > ```
>> > vector x_coords = {0, 10, 20};
>> > vector y_coords = {0, 5};
>> > vector z_coords = {15, 10, 5, 20};
>> > auto r = views::cartesian_product(x_coords, y_coords, z_coords);
>> > auto ms1 = mdspan(from_range, r, x_coords.size(), y_coords.size(),
>> z_coords.size());
>> >
>> > std::vector posX = {0.0, 1.0, 2.0, 3.0};
>> > std::vector posY = {0.0, 0.5, 1.0, 1.5};
>> > std::vector mask = {1, 0, 1, 0};
>> > auto ms2 = mdspan(from_range, std::views::zip(posX, posY, mask), 2,
>> 2);
>> > ```
>> >
>> > I think this clearly shows that the user intends to make mdspans from
>> ranges rather than a raw pointer.
>>
>> I appreciate your thinking and your wish to make C++ better : - ) .
>>
>> Using `from_range` might suggest that a container is being constructed
>> "from" a range. That's not what's happening here. The mdspan is a
>> view of the range itself. It's not "from" a range, it IS the range,
>> just a multidimensional view of it.
>>
>> [range.utility.conv.general] 1: "The range conversion functions
>> construct an object (usually a container) from a range, by using a
>> constructor taking a range, a from_range_t tagged constructor, or a
>> constructor taking a pair of iterators, or by inserting each element
>> of the range into the default-constructed object."
>>
>> > And there is no spelling of .begin(), and no duplicate spelling, which
>> looks satisfactory to me.
>> > What do you think?
>>
>> Have you considered just adding nonmember functions that create an
>> mdspan with the desired properties, such as the following?
>>
>> template<class Range, size_t... Extents> requires( /* ... */ )
>> constexpr auto make_mdspan_from_range(Range&& r, Extents... exts) {
>> auto beg = r.begin();
>> return mdspan(beg, layout_right::mapping{exts...},
>> iterator_accessor<decltype(beg)>());
>> }
>>
>> Why is it so important to add a special-case constructor to mdspan for
>> this use case?
>>
>> mfh
>>
>> >
>> > Hewill
>> >
>> >
>> > Hewill Kang <hewillk_at_[hidden]> 於 2026年4月4日週六 下午5:22寫道:
>> >>
>> >> Currently, the creation of `mdspans` with `iterator_accessor` relies
>> on the following CTAD:
>> >> ```
>> >> template<class MappingType, class AccessorType>
>> >> mdspan(const typename AccessorType::data_handle_type&, const
>> MappingType&,
>> >> const AccessorType&)
>> >> -> mdspan<typename AccessorType::element_type, typename
>> MappingType::extents_type,
>> >> typename MappingType::layout_type, AccessorType>;
>> >> ```
>> >>
>> >> We must spell with
>> >> ```
>> >> auto _3x3 = std::layout_right::mapping(std::extents(3, 3));
>> >> auto ms = std::mdspan(r.begin(), _3x3,
>> std::iterator_accessor(r.begin()));
>> >> ```
>> >>
>> >> A subsequent question is, once we have an `iterator_accessor`, should
>> we enhance mdspan's CTAD so that:
>> >>
>> >> ```
>> >> auto ms = std::mdspan(r.begin(), 3, 3); // call begin() instead of
>> data()
>> >> ```
>> >>
>> >> Works well? For example, enhance `ElementType*` parts in the current
>> CTAD for `random_access_iterator`:
>> >> ```
>> >> template<random_access_iterator I, class... Integrals>
>> >> requires ((is_convertible_v<Integrals, size_t> && ...) &&
>> sizeof...(Integrals) > 0)
>> >> explicit mdspan(I, Integrals...)
>> >> -> mdspan<typename iterator_accessor<I>::element_type,
>> >> extents<size_t, maybe-static-ext<Integrals>...>,
>> >> layout_right,
>> >> iterator_accessor<I>>;
>> >> ```
>> >> This eliminates the need for spelling of r.begin() twice.
>> >> However, it's uncertain whether this relaxation for users dealing with
>> contiguous_iterator/random_access_iteratorer instead of a raw pointer is
>> good.
>> >>
>> >> Hewill Kang <hewillk_at_[hidden]> 於 2026年4月4日週六 上午11:54寫道:
>> >>>
>> >>> Thank you, that makes sense.
>> >>> I updated the paper.
>> >>>
>> >>>
>> >>>
>> >>>
>> >>> Mark Hoemmen <mark.hoemmen_at_[hidden]> 於 2026年4月4日週六 上午3:16寫道:
>> >>>>
>> >>>> On Fri, Apr 3, 2026 at 11:27 AM Hewill Kang <hewillk_at_[hidden]>
>> wrote:
>> >>>> >>
>> >>>> >> Also, iterator_accessor has to deal with pointers as a special
>> case of
>> >>>> >> iterators, so it should have the same safeguard as
>> default_accessor and
>> >>>> >> friends against converting a pointer-to-derived to a
>> pointer-to-base.
>> >>>> >
>> >>>> >
>> >>>> > That's reasonable, thank you. I updated the paper.
>> >>>> >
>> >>>> > This is the current final version of the paper:
>> https://isocpp.org/files/papers/P4173R0.html
>> >>>> > And implementation with libstdc++: https://godbolt.org/z/86MKa3oa5
>> >>>>
>> >>>> Would you consider making those conversion operators to
>> >>>> `default_accessor` explicit?
>> >>>>
>> >>>> I ask because container authors sometimes provide iterators that
>> model
>> >>>> `contiguous_iterator` but do run-time bounds checking. Implicit
>> >>>> conversion from `iterator_accessor` to `default_accessor` would
>> >>>> silently strip away this protection.
>> >>>>
>> >>>> Thanks!
>> >>>> mfh
>> >>>>
>> >>>>
>> >>>> >
>> >>>> > Thanks.
>> >>>> > Hewill
>> >>>> >
>> >>>> > Ell <ell.ell.se_at_[hidden]> 於 2026年4月3日週五 上午2:48寫道:
>> >>>> >>
>> >>>> >> On Thursday, April 2nd, 2026 at 8:24 PM, Mark Hoemmen via
>> Std-Proposals <std-proposals_at_[hidden]> wrote:
>> >>>> >>
>> >>>> >> > >> Have you considered adding converting constructors so that
>> users can
>> >>>> >> > >> go from an iterator-to-nonconst to an iterator-to-const?
>> >>>> >> > >
>> >>>> >> > > The proposed wording has the following converting
>> constructors:
>> >>>> >> > >
>> >>>> >> > > template<convertible_to<I> I2>
>> >>>> >> > > constexpr iterator_accessor(iterator_accessor<I2>)
>> noexcept {}
>> >>>> >> > >
>> >>>> >> > > I think it's enough to cover it?
>> >>>> >> >
>> >>>> >> > The analog of this is enough for `default_accessor`, but that
>> only has
>> >>>> >> > to deal with pointers. If I is constructible from I2, but I2
>> is not
>> >>>> >> > convertible to I, then the same should be true of the
>> accessor. As a
>> >>>> >> > result, mdspan conversion would work in the same way as accessor
>> >>>> >> > conversion. This is the design intent of mdspan.
>> >>>> >> >
>> >>>> >>
>> >>>> >> Also, iterator_accessor has to deal with pointers as a special
>> case of
>> >>>> >> iterators, so it should have the same safeguard as
>> default_accessor and
>> >>>> >> friends against converting a pointer-to-derived to a
>> pointer-to-base.
>>
>

Received on 2026-04-06 05:54:17