如何迭代 mdspan?

How do I iterate an mdspan?

提问人:einpoklum 提问时间:3/19/2023 最后编辑:einpoklum 更新时间:11/16/2023 访问量:493

问:

因此,我决定使用 mdspan 而不是普通 span + 元素访问函数的组合。但是 - 我想用我的 mdspan 做的一件显而易见的事情是迭代它的元素。这就是我对 1D 跨度的处理方式:

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';

...但不是 's(使用 Kokkos 实现):mdspan

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

在 GodBolt(使用 GCC 中继)中尝试上述内容,我得到

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

所以,mdspans 似乎不是范围 - 即使它们是一维的(我什至希望迭代 2D 或 3D spans......怎么会这样?我该如何迭代它们?

C++ 迭代 C++23 范围循环 mdspan

评论


答:

2赞 einpoklum 3/19/2023 #1

似乎您需要像普通 C 数组一样迭代一个:mdspan

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';

...多维跨度也是如此 - 就像多维 C 数组一样,但使用多参数: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';
}

请参阅工作中的第二个示例:。GodBolt

现在,我不确定为什么你不能直接迭代 - 因为 肯定可以迭代。也许这个想法是为了强调内存中的顺序如何得不到保证?我想知道。mdspan

3赞 Quxflux 3/19/2023 #2

迭代器支持很早就从处理规范的论文转移到了另一篇论文(似乎没有任何进展)。mdspanmdspan

被投票到 C++23 中的提案既不包含迭代器,也不允许创建对象切片(后者正在积极开发中)。mdspansubmdspansmdspan

有一种解决方法可以创建一个可迭代的视图,该视图可以与使用 C++23 的算法一起使用:mdspanstd::rangesviews::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';

坏消息是,这种解决方法比直接对基础数据进行操作要慢得多,因为编译器似乎无法优化索引计算,如以下示例所示:https://godbolt.org/z/7a4T6KxY6

另一件有用的事情是嵌入到类型中的布局信息。 当 a 是时,我的理解是,可以安全地假设基础数据是连续的,因此可以安全地转换为可以与算法一起使用的:mdspanlayout_typemdspanlayout_rightdata_handle()std::spanstd::ranges

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()};
}

评论

1赞 Red.Wave 8/15/2023
这基本上意味着不要使用 mdspan。一致性在哪里?它是作为视图启动的;现在只是不满足视图的要求。
0赞 Quxflux 8/16/2023
@Red.Wave:我不会说不要使用 mdspan。它仍然作为一种词汇表类型,允许使用来自不同来源的多维数据(想象一下,允许从、传递矩阵,所有这些都使用一个接口)。使用模板“元数据”,似乎仍然有实现良好性能的方法,只是不是开箱即用。也许人们会在第一个实现进入 std 库后立即找到更好的方法来处理这个问题。eigenopencvippmdspan
0赞 Red.Wave 8/16/2023
最好使用适当的范围算法和基于范围来避免越界索引的风险。如果你需要定义自己的包装器,那么重新定义会更便宜。否则,仅仅用于索引的语法糖没有任何吸引力。具体来说,如果应该有接近这种的东西,那就不值得麻烦了。formdspanmdarraymdspan