Range-v3运算符重载编写更短的代码

25

对于我的矩阵类,我想在range-v3视图上进行某种运算符重载(可能使用表达式模板)来实现+ - / * %。例如,如果我想获得两个列之和的视图,我想写成

col_1 + col_2

取而代之

rv::zip_with([](auto c1, auto c2) {return c1 + c2;}, col_1, col_2);

可能可以从这篇论文中得到一些想法,以避免构造过多的临时变量。这是我想编写的代码:

//simple example
//what I want to write
auto rangeview =    col_1 + col_2;
//what I can write
auto rangeview =    rv::zip_with([](auto c1, auto c2) {
                        return c1 + c2;
                    }, col_1, col_2);


//itermediate
//what I want to write
auto rangeview =    col_1 + col_2 + col_3;
//what I can write
auto rangeview =    rv::zip_with([](auto c1, auto c2, auto c3) {
                        return c1 + c2 + c3;
                    }, col_1, col_2, col_3);


//advanced
//what I want to write
auto rangeview =    10*col_1 + 20*col_2 - 30*col_3;
//what I can write
auto rangeview =    rv::zip_with([](auto c1, auto c2, auto c3) {
                        return 10.0*c1 + 20.0*c2 - 30.0*c3;
                    }, col_1, col_2, col_3);


//more advanced with elementwise multiplication
//what I want to write
auto rangeview =    10*col_1 + 20*col_2 - col_2 % col_3;
//what I can write
auto rangeview =    rv::zip_with([](auto c1, auto c2, auto c3) {
                        return 10.0*c1 + 20.0*c2 - c2*c3;
                    }, col_1, col_2, col_3);

6
这是一个非常宽泛的问题。你实际上在寻求一个表达式模板库,而且有很多大型库在各个领域都试图解决这种问题。 - Barry
中间的例子呢(只是添加任意数量的视图)? 仍然太宽泛了吗? - Porsche9II
常规运算符重载有什么问题? - Serge
使用常规的重载,你必须像论文中(第11页)所解释的那样构造临时变量。 - Porsche9II
你已经链接了一篇推广一个已经实现此功能的库的论文。该论文描述了它如何进行加法,你只需要重复这个过程来实现其他操作。自己编写将会非常繁琐。 - Caleth
显示剩余2条评论
3个回答

1

范围视图被认为是惰性的;这难道不是它们被称为视图的原因吗?那么为什么不将结果视图返回到自己定义的操作数中呢?理想情况下,它们将在for循环或算法中迭代最终范围时进行评估。

我认为并非所有东西都可以由std或任何其他通用库提供。使用任何工具都需要进行一些小的调整:

decltype(auto) operator+(column const& col_1, column const& col_2){
    return rv::zip_transform( [] (auto c1, auto c2)
                                 {return c1 + c2;},
                             rv::all(col_1),
                             rv::all(col_2) );
};

标量*向量乘法运算符也可以类似的分开定义,然后列的线性组合就可以按照数学上的写法直接书写。实际的计算要等到for循环或像std::fold、std::foreach这样的算法遍历范围时再进行。


惰性标量*向量乘积变成:

decltype(auto) operator*(range_value_t<column> const coef, column& col) {
    return rv::transform(rv::all(col),[=,coef](auto x){return coef*x});
};

这个方法很好,我目前在我的代码中使用类似的方法。但是像10*col_1 + 20*col_2 - 30*col_3这样的操作会生成更多的临时变量,而rv::zip_with([](auto c1, auto c2, auto c3) {return 10.0*c1 + 20.0*c2 - 30.0*c3;}, col_1, col_2, col_3);则不会。 - Porsche9II
即使是惰性求值也不是免费的。任何惰性库都会为乘法存储标量和向量的视图。最简单的解决方案是使用捕获lambda作为函数对象的系数来return rv::transform。如果您的矩阵维数可以用手指数清点,那么这种方法就不适用了。也许小值优化可以帮助,如果这将成为一个非常通用的库。 - Red.Wave
rv::all 应该会获得原始视图 - 可能是容器 - 视图。粗略地说,它只是一对 [begin,end) 迭代器。只要使用视图,临时对象就保持紧凑。 - Red.Wave
你有使用zip_transform的工作编译器Explorer示例吗? - Porsche9II
还没有。我需要再等一年左右。Ranges.v3缺少一些功能。但是希望zip家族会在C++23中出现。 - Red.Wave
内积变为 rv::zip_transform([](auto x, auto y){return x*y;},rv::all(vec1), rv::all(vec2)) | rv::fold([](auto v1, auto v2){return x+y;}) - Red.Wave

0

我同意 @darune 的观点,除非你有强烈的需求,否则应该使用一个库,特别是 Eigen。你想要支持的大多数操作都在 这个链接 上有说明。


我正在使用Armadillo处理我的数值矩阵 - 但我需要这个功能来处理单元格矩阵,其中每个单元格都是std::string、double、bool等的std::variant。 - Porsche9II
这是类似于数据框架的东西吗?从上面你得到了10.0*c1 + 20.0*c2 - c2*c3;,你是在对std::string还是std::bool进行操作? - Mike MacNeil
把它想象成一个Excel-单元格矩阵,其中一些列是数字的,但其他列不是... - Porsche9II
1
好的,那么在这种情况下,你需要实现自己的表达式模板来避免复制,这并不是一件简单的事情,需要付出一定的努力(这就是Eigen和Armadillo都在做的事情)。但是复制也不是可怕的事情,例如numpy运行非常快,并且到处都会进行复制。当您需要对某些运算符进行数百万次操作时,表达式模板更加有用。 - Mike MacNeil
@Porsche9II 电子表格程序确实会缓存值(实际上有些情况下重新计算会被暂停,例如将电子表格作为MIME对象包含在另一个文档中)。它们使用“公式”对象的概念。此外,电子表格通常具有相对和绝对寻址,这是自第一个电子表格程序以来的标准行为。例如,在Excel中,包含“A1”的公式与“$A$1”不同。前者如果复制到其他单元格中会发生变化。 - Swift - Friday Pie

0
你主要关注的问题似乎是可能会创建大量临时变量。这可以通过一个叫做“表达式模板”的概念来解决,但实际上并没有一种适用于所有情况的解决方案。最好的选择可能是转向支持此功能的矩阵库。除非你正在开发一个库或想要投资于此,否则最好避免编写自己的表达式模板。一个更简单的方法是使用基于范围的for循环和自定义迭代类型(不像表达式模板那样语法糖化,但实现起来要容易得多):
伪代码:
*resultype* result;//perhaps alloc on this line or make zip handle it..
for (auto z : zip(result, col_1, col_2, col_3)) {
  z[0] = 10.0*z[1] + 20.0*z[2] - 30.0*z[3];
}

这只是一个想法阶段,但会比lambda风格更短一些(并且在我看来更加舒适阅读)。 如何实现zip类,我将在本答案中略去。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接