5

So, I decided I want to use mdspan's rather than a span + element access function. But - one obvious thing one would want to do with an (md)span is iterate its elements. This works for spans:

std::vector vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
auto sp = std::span(vec.data(), 12);
for (auto x : sp) {
    std::cout << x << ' ';
}
std::cout << '\n';

... but not for mdspan's (using the Kokkos implementation):

std::vector vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
auto ms = std::experimental::mdspan(vec.data(), 12);
for (auto x : ms) {
    std::cout << x << ' ';
}
std::cout << '\n';

Trying the above in GodBolt (with GCC trunk), I get:

<source>:10:19: error: 'begin' was not declared in this scope
   10 |     for (auto x : ms) {
      |                   ^~

so, it seems mdspans are not ranges - even if they're one-dimensional (and I was even hoping to iterate 2D or 3D spans...) what gives?

einpoklum
  • 118,144
  • 57
  • 340
  • 684

2 Answers2

2

The iterator support for mdspan has been early on moved from the mdspan paper to deal with the specification to another paper (for which there does not seem to be any progress).

The mdspan proposal that has been voted into C++23 does neither contain iterators nor submdspans which would allow for creating slices of mdspan objects (on the latter there is being actively worked on).

There is a workaround to create a iterateable view on mdspan which can be used with algorithms from std::ranges using C++23 views::cartesian_product:

std::vector vec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
auto ms = std::experimental::mdspan(vec.data(), 2, 6);

auto coeff_view =
    std::views::cartesian_product(std::views::iota(0uz, ms.extent(0)),
                                    std::views::iota(0uz, ms.extent(1))) |
    std::views::transform([ms](const auto index) {
        const auto [r, c] = index;
        return ms[r, c];
    });

std::cout << std::ranges::max(coeff_view) << '\n';

The bad news is that this workaround is way slower than operating on the underlying data directly as the index calculation seemingly cannot be optimized by the compiler as seen in this example: https://godbolt.org/z/7a4T6KxY6.

Another thing that can be useful is the layout information baked into the mdspan type. When the layout_type of a mdspan is layout_right it is my understanding that it is safe to assume that the underlying data is contiguous and thus data_handle() can be safely transformed into a std::span which can be used with std::ranges algorithms:

template <typename Mdspan>
concept mdspan_specialization = std::same_as<
    Mdspan, std::experimental::mdspan<
                typename Mdspan::element_type, typename Mdspan::extents_type,
                typename Mdspan::layout_type, typename Mdspan::accessor_type>>;

template <mdspan_specialization Mdspan>
    requires(std::same_as<typename Mdspan::layout_type,
                          std::experimental::layout_right>)
auto to_span(const Mdspan ms) {
    return std::span{ms.data_handle(), ms.size()};
}
Quxflux
  • 3,133
  • 2
  • 26
  • 46
  • This basically means **don't use `mdspan`**. Where's the consistency? It was initiated as a *view*; Now just doesn't satisfy the requirements of a view. – Red.Wave Aug 15 '23 at 15:09
  • @Red.Wave I wouldn't go that far to say *don't use `mdspan`*. It still serves as a vocabulary type, allowing to consume multidimensional data from different sources (imagine allowing to pass matrices from `eigen`, `opencv`, `ipp` all with a single `mdspan` interface). With the template "metadata" there still seem to be ways to achieve good performance, just not out of the box. Maybe people will find better ways to deal with this as soon as first implementations make their way into the std libraries. – Quxflux Aug 15 '23 at 18:13
  • The risk of out of bounds indexing is best avoided using proper range algorithms and range-based `for`. If you need to define own wrappers, then redefining `mdspan` would be cheaper. Otherwise mere syntax sugar for indexing is not any appealing. Specifically if `mdarray` is supposed to be anything near this kind of `mdspan`, it is not worth the trouble. – Red.Wave Aug 15 '23 at 20:09
1

It seems you need to iterate an mdspan just like a plain C array:

std::vector vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
auto ms = std::experimental::mdspan(vec.data(), 12);
for (auto i : std::views::iota(0uz, ms.extent(0))) {
    std::cout << ms[i] << ' ';
}
std::cout << '\n';

... and the same goes for multi-dimensional spans - just like multidimensional C arrays, but with the multi-argument operator[]:

std::vector vec = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
auto ms = std::experimental::mdspan(vec.data(), 2, 6);
for (auto r : std::views::iota(0uz, ms.extent(0))) {
    for (auto c : std::views::iota(0uz, ms.extent(1))) {
        std::cout << ms[r, c] << ' ';
        // ... and note that ms[r][c] won't work! :-(
    }
    std::cout << '\n';
}

See this second example at work: GodBolt.

Now, I'm not sure why you can't just directly iterate - since mdspan's could definitely have been made iterable. Perhaps the idea is to emphasize how the order in memory is not guaranteed? I wonder.

einpoklum
  • 118,144
  • 57
  • 340
  • 684