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

关于constexpr函数中同时计算std::array大小与值的可行性,及编译期稀疏矩阵CSC结构操作代码简化的咨询

在constexpr函数中同时计算std::array大小与值,简化编译期稀疏矩阵CSC结构操作代码

很高兴看到你在尝试用编译期模板和constexpr实现稀疏矩阵的结构操作,这个思路真的很赞!你提到的get_mat_vec_nnz和get_mat_vec_row_indices重复计算的问题,确实是这类编译期编程里常见的痛点,不过我们有几种简洁的方法可以解决它,既能保留constexpr特性,又能彻底消除冗余代码。

核心思路:用编译期结构体封装双重结果

既然我们需要同时得到非零元素数量和行索引数组,不如直接写一个constexpr函数,返回一个包含这两个信息的自定义结构体。这样只需要一次遍历计算,就能同时拿到两个模板参数需要的值。

步骤1:定义编译期结果结构体

首先我们定义一个模板结构体,用来打包非零元素数量和行索引:

template <size_t... RowIndices>
struct MatVecStructureResult {
    // 非零元素数量直接由模板参数的个数推导
    static constexpr size_t nnz = sizeof...(RowIndices);
    // 行索引数组直接从模板参数初始化
    static constexpr std::array<size_t, nnz> row_indices = {RowIndices...};
};

步骤2:实现单次遍历的constexpr生成函数

接下来写一个递归的constexpr函数(或者用C++20的模板lambda递归),遍历矩阵和向量的结构,收集所有非零的行索引,最后打包成上面的结构体:

// 假设Lhs是你的CscStructure类型,Rhs是向量的稀疏结构(比如包含row_indices的结构体)
template <typename Lhs, typename Rhs, size_t CurrentCol = 0, size_t... CollectedRows>
constexpr auto generate_mat_vec_structure() {
    // 遍历完所有列,返回结果结构体
    if constexpr (CurrentCol == Lhs::n_cols) {
        return MatVecStructureResult<CollectedRows...>{};
    } else {
        // 获取当前列的行索引范围(从column_ptrs中取)
        const size_t col_start = Lhs::column_ptrs[CurrentCol];
        const size_t col_end = Lhs::column_ptrs[CurrentCol + 1];

        // 用模板lambda递归遍历当前列的每个行索引
        auto process_current_col = [&]<size_t CurrentIdx = col_start>(auto self) {
            if constexpr (CurrentIdx == col_end) {
                // 当前列处理完,进入下一列
                return generate_mat_vec_structure<Lhs, Rhs, CurrentCol + 1, CollectedRows...>();
            } else {
                const size_t current_row = Lhs::row_indices[CurrentIdx];
                // 检查当前行是否在向量的非零元素中(编译期判断)
                bool is_nonzero = false;
                for (size_t rhs_row : Rhs::row_indices) {
                    if (rhs_row == current_row) {
                        is_nonzero = true;
                        break;
                    }
                }

                if constexpr (is_nonzero) {
                    // 非零则将行索引加入结果,继续遍历下一个索引
                    return self.template operator()<CurrentIdx + 1>(self);
                } else {
                    // 零元素则跳过,继续遍历
                    return self.template operator()<CurrentIdx + 1>(self);
                }
            }
        };

        return process_current_col(process_current_col);
    }
}

步骤3:使用结果生成目标CSC结构

现在你只需要调用一次生成函数,就能同时拿到nnz和row_indices,直接用于实例化CscStructure:

// 假设LhsStruct是你的输入CSC矩阵结构,RhsStruct是输入向量的稀疏结构
constexpr auto mat_vec_result = generate_mat_vec_structure<LhsStruct, RhsStruct>();

// 实例化结果的CSC结构(矩阵-向量乘积是向量,所以列数为1,column_ptrs是{0, nnz})
using MatVecStructure = CscStructure<
    LhsStruct::n_rows,
    1, // 向量是单列
    mat_vec_result.nnz,
    std::array<size_t, 2>{0, mat_vec_result.nnz},
    mat_vec_result.row_indices
>;

为什么不能用std::vector?

你说得没错,std::vector即使在constexpr中创建,它的size()也无法作为模板参数。因为模板参数要求是编译期已知的常量表达式,而std::vector的大小本质上是运行时动态分配的(哪怕在constexpr中它的大小是固定的,编译器也无法将其视为编译期常量来推导模板参数)。所以必须用std::array或者编译期序列(比如std::integer_sequence)来存储编译期确定的数组。

兼容性说明

上面的代码用到了C20的模板lambda和constexpr lambda递归,如果你的编译器只支持C17,可以把递归逻辑改成普通的递归模板函数,核心思路是一样的——用一个结构体打包结果,避免重复计算。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 07:52:59