在面向数字的语言(Matlab,Fortran)中,范围运算符和语义在处理多维数据时非常方便。 例如:
A(i:j,k,:n) // represents two-dimensional slice B(i:j,0:n) of A at index k
遗憾的是,C ++没有范围运算符(:)。当然它可以使用范围/切片仿函数进行仿真,但语义不如Matlab干净。我在C ++中使用矩阵/张量域语言进行原型设计,我想知道是否有任何选项可以重现范围运算符。 我仍然只想依赖C ++ / prprocessor框架。
到目前为止,我已经查看了可能是合适选项的增强波。
还有其他方法可以将新的非本地运营商引入C ++ DSL吗?
我知道你不能添加新的operator.am专门寻找变通方法。 我提出了一件事(非常难看的黑客,我不打算使用):
#define A(r) A[range(((1)?r), ((0)?r))] // assume A overloads []
A(i:j); // abuse ternary operator
答案 0 :(得分:3)
我之前使用过的解决方案是编写一个外部预处理器来解析源代码并用vanilla C ++替换自定义运算符的任何用法。出于您的目的,a : b
用途将替换为a.operator_range_(b)
和operator:()
声明,声明为range_ operator_range_()
。在makefile中,然后添加一个规则,在编译源文件之前对其进行预处理。这可以在Perl中相对容易地完成。
但是,过去曾使用类似的解决方案,我不推荐它。如果您不对源的处理和生成方式保持警惕,它有可能产生可维护性和可移植性问题。
答案 1 :(得分:2)
不 - 您无法在C ++中定义自己的运算符。 Bjarne Stroustrup details why.
答案 2 :(得分:2)
正如比利所说,你不能超载运营商。但是,你可以通过“常规”运算符重载(也许是一些模板元编程)非常接近你想要的东西。允许这样的事情很容易:
#include <iostream>
class FakeNumber {
int n;
public:
FakeNumber(int nn) : n(nn) {}
operator int() const { return n; }
};
class Range {
int f, t;
public:
Range(const int& ff, const int& tt) : f(ff), t(tt) {};
int from() const { return f; }
int to() const { return t; }
};
Range operator-(const FakeNumber& a, const int b) {
return Range(a,b);
}
class Matrix {
public:
void operator()(const Range& a, const Range& b) {
std::cout << "(" << a.from() << ":" << a.to() << "," << b.from() << ":" << b.to() << ")" << std::endl;
}
};
int main() {
FakeNumber a=1,b=2,c=3,d=4;
Matrix m;
m(a-b,c-d);
return 0;
}
缺点是此解决方案不支持全文字表达式。来自或必须是用户定义的类,因为我们不能为两个基本类型重载operator.
您也可以重载operator*
以允许指定步进,如下所示:
m(a-b*3,c-d); // equivalent to m[a:b:3,c:d]
重载两个版本的operator--
以允许忽略其中一个边界:
m(a--,--d); // equivalent to m[a:,:d]
另一种选择是定义两个对象,名称类似于Matrix :: start和Matrix :: end,或者你喜欢的任何东西,然后你可以使用它们而不是operator--
,然后另一个绑定不必是变量,可以是文字:
m(start-15,38-end); // This clutters the syntax however
你当然可以两种方式使用。
我认为,如果不采用奇怪的解决方案,例如自定义预建工具或宏滥用(Matthieu提出并建议不要使用它们),这几乎是你能得到的最佳效果。)。
答案 3 :(得分:2)
另一种方法是使用程序转换工具构建C ++变体方言。
DMS Software Reengineering Toolkit是一个程序转换引擎,具有工业实力C++ Front End。 DMS,使用这个前端,可以解析完整的C ++(它甚至有一个预处理器,可以保留大多数预处理器指令未扩展),自动构建AST和完整的符号表。
C ++前端使用直接从标准派生的语法来源。添加新的语法规则在技术上是直截了当的,包括那些允许“:”语法作为数组下标的那些,如你所描述的那样,并且已经实现了Fortran90 +。然后,可以使用DMS的程序转换功能将“新”语法转换为“vanilla”C ++,以便在传统的C ++编译器中使用。 (该方案是“将DSL概念添加到您的语言中”的故意编程模型的概括。)。
我们实际上使用这种方法进行了“Vector C ++”的概念演示。
我们添加了一个多维Vector数据类型,其存储语义只是数组元素是不同的。这与C ++的顺序位置模型不同,但如果您希望编译器/变换器可以自由地任意布局内存,则需要这种不同的语义,如果您想使用SIMD机器指令和/或高效的缓存访问,这是必不可少的。沿着不同的轴。
我们添加了Fortran-90样式标量和子阵列范围访问,几乎添加了所有F90的阵列处理操作,增加了很多APL的矩阵运算,所有这些都是通过调整DMS C ++语法实现的。
最后,我们使用DMS转换功能构建了两个转换器:一个将其中的一个重要部分(请记住,这是一个概念演示)映射到vanilla C ++,这样您就可以在典型的工作站上编译和运行Vector C ++应用程序,而另一个将C ++映射到带有SIMD指令扩展的PowerPC C ++方言,我们生成的SIMD代码非常合理。花了我们大约6个人月来做这一切。
客户最终得到了保护(他的商业模式不包括支持自定义编译器,尽管他非常需要基于并行/ SIMD的操作),而且它已经在货架上萎靡不振。我们选择不在更广阔的市场中追求这一点,因为目前尚不清楚市场究竟是什么。我很确定有些组织对此有价值。
重点是,你真的可以做到这一点。使用临时方法几乎是不可能的。从技术上讲,它具有足够强大的程序转换系统,非常简单。这不是在公园散步。
答案 4 :(得分:1)
最简单的解决方案是在矩阵上使用方法而不是运算符。
A.range(i, j, k, n);
请注意,通常您不会在下标运算符,
中使用[]
,例如A[i][j]
而不是A[i,j]
。第二种形式可以通过重载逗号运算符来实现,但是你强制i
和j
成为对象而不是数字。
您可以定义一个range
类,它可以用作矩阵类的下标。
class RealMatrix
{
public:
MatrixRowRangeProxy operator[] (int i) {
return operator[](range(i, 1));
}
MatrixRowRangeProxy operator[] (range r);
// ...
RealMatrix(const MatrixRangeProxy proxy);
};
// A generic view on a matrix
class MatrixProxy
{
protected:
RealMatrix * matrix;
};
// A view on a matrix of a range of rows
class MatrixRowRangeProxy : public MatrixProxy
{
public:
MatrixColRangeProxy operator[] (int i) {
return operator[](range(i, 1));
}
MatrixColRangeProxy operator[] (const range & r);
// ...
};
// A view on a matrix of a range of columns
class MatrixColRangeProxy : public MatrixProxy
{
public:
MatrixRangeProxy operator[] (int i) {
return operator[](range(i, 1));
}
MatrixRangeProxy operator[] (const range & r);
// ...
};
然后你可以将范围从一个矩阵复制到另一个矩阵。
RealMatrix A = ...
RealMatrix B = A[range(i,j)][range(k,n)];
最后,通过创建一个可以容纳Matrix
或RealMatrix
的{{1}}类,您可以使MatrixProxy
和RealMatrix
看起来相同外面。
请注意代理上的MatrixProxy
不是虚拟的,也不能是虚拟的。
答案 5 :(得分:0)
如果您想玩得开心,可以查看IdOp。
如果你真的在做一个项目,我不建议使用这个技巧。维护将受到巧妙的伎俩。
因此,最好的选择是咬紧牙关并使用明确的表示法。一个名为range
的简短函数产生一个运算符过载的自定义对象,这似乎特别合适。
Matrix<10,30,50> matrix = /**/;
MatrixView<5,6,7> view = matrix[range(0,5)][range(0,6)][range(0,7)];
Matrix<5,6,7> n = view;
注意operator[]
只有4个重载(const / non-const + basic int / range)并产生一个代理对象(直到最后一个维度)。一旦应用到最后一个维度,它就会给出矩阵的视图。可以从具有相同维度的视图(非显式构造函数)构建普通矩阵。