关于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

