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:29:17 +0800
>
> 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:29:31