You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++23及后续版本如何用索引向量简洁提取mdspan元素?

针对C++23/未来标准下mdspan切片与编译期结果数组的解决方案

核心限制与现状

当前C23的std::mdspan本身不直接支持运行时索引向量的切片语法(如md[ind1,:]),即将到来的submdspan(C26提案)主要面向编译期已知的切片范围,无法直接适配std::vector<std::size_t>这类运行时动态生成的索引列表。但结合C++23的ranges/views工具链,可实现接近需求的简洁语法,同时确保最终结果为编译期大小已知的std::array。

实现思路与代码示例

1. 构建索引视图与切片适配器

实现通用切片适配器,将运行时索引向量转换为range视图,结合mdspan的元素访问生成子矩阵视图:

#include <iostream>
#include <array>
#include <vector>
#include <ranges>
#include <mdspan>

// 行切片:提取指定行的所有列
template<class MdSpan, class Indices>
auto slice_rows(MdSpan md, const Indices& row_indices) {
    return row_indices | std::views::transform([md](std::size_t i) {
        return std::views::iota(0u, md.extent(1)) | std::views::transform([md, i](std::size_t j) {
            return md[i, j];
        });
    });
}

// 列切片:提取指定列的所有行
template<class MdSpan, class Indices>
auto slice_cols(MdSpan md, const Indices& col_indices) {
    return std::views::iota(0u, md.extent(0)) | std::views::transform([md, &col_indices](std::size_t i) {
        return col_indices | std::views::transform([md, i](std::size_t j) {
            return md[i, j];
        });
    });
}

// 子矩阵切片:提取行列组合的元素(支持重复索引)
template<class MdSpan, class RowIndices, class ColIndices>
auto slice_submatrix(MdSpan md, const RowIndices& row_indices, const ColIndices& col_indices) {
    return row_indices | std::views::transform([md, &col_indices](std::size_t i) {
        return col_indices | std::views::transform([md, i](std::size_t j) {
            return md[i, j];
        });
    });
}

2. 适配矩阵操作函数,生成编译期大小的std::array

让multiply()和add()支持range视图输入,并输出编译期大小固定的std::array:

// 矩阵乘法:输入M×K、K×N视图,输出M×N的std::array
template<std::size_t M, std::size_t K, std::size_t N, class View1, class View2>
constexpr std::array<std::size_t, M*N> multiply(const View1& mat1, const View2& mat2) {
    std::array<std::size_t, M*N> result{};
    for (std::size_t i = 0; i < M; ++i) {
        for (std::size_t j = 0; j < N; ++j) {
            std::size_t sum = 0;
            for (std::size_t k = 0; k < K; ++k) {
                sum += *std::next(mat1[i].begin(), k) * *std::next(mat2[k].begin(), j);
            }
            result[i*N + j] = sum;
        }
    }
    return result;
}

// 矩阵加法:输入两个M×N视图,输出M×N的std::array
template<std::size_t M, std::size_t N, class View1, class View2>
constexpr std::array<std::size_t, M*N> add(const View1& mat1, const View2& mat2) {
    std::array<std::size_t, M*N> result{};
    for (std::size_t i = 0; i < M; ++i) {
        for (std::size_t j = 0; j < N; ++j) {
            result[i*N + j] = *std::next(mat1[i].begin(), j) + *std::next(mat2[i].begin(), j);
        }
    }
    return result;
}

3. 简洁调用示例

结合适配器与操作函数,可写出接近预期的表达式:

int main() {
    constexpr std::size_t pDeg = 16;
    std::array<std::size_t, pDeg*pDeg> arr;
    for (std::size_t i = 0; i < arr.size(); ++i)
        arr[i] = i;

    auto md = std::mdspan<std::size_t, std::extents<std::size_t, pDeg, pDeg>>(arr.data());

    // 若需编译期确定结果大小,索引集合需为constexpr
    constexpr std::array<std::size_t, 8> ind1 = {1,5,6,7,8,9,4,5};
    constexpr std::array<std::size_t, 8> ind2 = {14,11,12,13,11,1,5,6};

    // 生成切片视图
    auto mat_rows = slice_rows(md, ind1);
    auto mat_cols = slice_cols(md, ind2);
    auto mat_sub = slice_submatrix(md, ind1, ind2);

    // 简洁矩阵操作表达式,结果为编译期大小的std::array
    auto result = add<8,8>(multiply<8,16,8>(mat_rows, mat_cols), mat_sub);

    // 输出验证
    for (std::size_t val : result) {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    return 0;
}

动态索引的适配方案

若索引必须为运行时动态生成(如std::vector),需将其转换为编译期大小已知的容器(如std::array,通过模板参数指定大小),或使用std::span<const std::size_t, N>传递固定大小的索引集合,才能保证输出为编译期大小的std::array。

未来标准的优化方向

C26的submdspan目前仍聚焦编译期切片范围,社区已有动态索引切片的讨论;std::mdarray(C26)作为mdspan的容器版本,未来可能结合ranges提供更灵活的动态切片语法,但暂不支持md[ind1,:]这类Python风格语法。

内容的提问来源于stack exchange,提问作者Astor

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 05:05:23