我目前有一个类层次结构,如
MatrixBase -> DenseMatrix
-> (other types of matrices)
-> MatrixView -> TransposeView
-> DiagonalView
-> (other specialized views of matrices)
MatrixBase
是一个抽象类,它强制实现者定义operator()(int,int)等等;它代表二维数字数组。 MatrixView
代表一种(可能是可变的)查看矩阵的方式,例如转置它或采用子矩阵。 MatrixView
的要点是能够说出类似
Scale(Diagonal(A), 2.0)
其中Diagonal
返回一个DiagonalView
对象,这是一种轻量级适配器。
现在是问题所在。我将使用一个非常简单的矩阵运算作为例子。我想定义一个像
这样的函数template <class T>
void Scale(MatrixBase<T> &A, const T &scale_factor);
这就是名字所暗示的显而易见的事情。我希望能够传递一个诚实到善的非视图矩阵,或MatrixView
的子类的实例。上面写的原型不适用于
Scale(Diagonal(A), 2.0);
因为DiagonalView
返回的Diagonal
对象是临时的,Scale
采用非const引用,不能接受临时引用。有没有办法让这项工作?我试图使用SFINAE,但我不太了解它,我不确定这是否能解决问题。对我来说很重要的是,可以在不提供显式模板参数列表的情况下调用这些模板化函数(我想要隐式实例化)。理想情况下,上述陈述可以按照书面形式发挥作用。
编辑:(跟进问题)
由于sbi在下面回答了关于右值引用和临时值的问题,有没有办法定义两个版本的Scale,一个对非视图采用非const rvalue引用,另一个采用按值传递视图?问题是在编译时区分这两者,使隐式实例化起作用。
更新
我已将类层次结构更改为
ReadableMatrix
WritableMatrix : public ReadableMatrix
WritableMatrixView
DenseMatrix : public WritableMatrix
DiagonalView : public WritableMatrixView
WritableMatrixView
与WritableMatrix
不同的原因是视图必须由const引用传递,而矩阵本身必须由非const引用传递,因此访问器成员函数具有不同的常数。现在像Scale这样的函数可以定义为
template <class T>
void Scale(const WritableMatrixView<T> &A, const T &scale_factor);
template <class T>
void Scale(WritableMatrix<T> &A, const T &scale_factor){
Scale(WritableMatrixViewAdapter<T>(A), scale_factor);
}
请注意,有两个版本,一个用于const视图,一个用于实际矩阵的非const版本。这意味着对于像Mult(A, B, C)
这样的函数,我需要8次重载,但至少它有效。然而,什么不起作用是在其他功能中使用这些功能。你看,每个View
- 类包含一个正在查看的成员View
;例如,在表达式Diagonal(SubMatrix(A))
中,Diagonal
函数返回类型为DiagonalView<SubMatrixView<T> >
的对象,该对象需要知道A
的完全派生类型。现在,假设在Scale
内我调用了类似的其他函数,它采用基本视图或矩阵引用。那会失败,因为所需的View
的构造需要Scale的参数的派生类型;它没有的信息。仍在努力寻找解决方案。
更新
我已经使用了本地版本的Boost的enable_if来在两个不同版本的函数Scale
之间进行选择。它归结为标记我的所有矩阵和视图类,其中包含额外的typedef标记,指示它们是可读写的还是视图或非视图。最后,我仍然需要2 ^ N个重载,但现在N只是非const参数的数量。对于最终结果,请参阅here(不太可能再次对其进行认真修改)。
答案 0 :(得分:7)
这与模板无关。你的例子
Scale(Diagonal(A), 2.0);
可以推广到
f(g(v),c);
在C ++ 03中,这要求每个副本或每个f()
引用传递const
的第一个参数。原因是g()
返回临时值和右值。但是,rvalues仅绑定到const
引用,但不绑定到非const引用。这与模板,SFINAE,TMP或其他内容无关。这就是语言(目前)的方式。
还有一个基本原理:如果g()
返回一个临时的,f()
修改了临时,那么没有人有机会“看到”修改后的临时。因此修改是徒劳的,整个事情很可能是一个错误。
据我所知,在你的情况下,g()
的结果是一个临时的,它是对某个其他对象(v
)的一个视图,所以修改它会修改v
。但如果是这种情况,在当前的C ++中,g()
的结果必须是const
(以便它可以绑定到const
引用,或者必须复制它。因为{{ 1}}“气味”对我来说是错误的,这使得复制视图便宜可能是最好的。
但是,还有更多内容。 C ++ 1x将引入所谓的右值引用。我们所知的“引用”将被分为左值引用或右值引用。您将能够使用rvalue引用函数,甚至基于“l / rvalue-ness”进行重载。这被认为是允许类设计者重载rvalue右侧的复制ctor和赋值,并让它们“窃取”右侧的值,这样复制rvalues会更便宜。但你可以用它来const
取一个右值并修改它。
不幸的是,你的编译器很可能还不支持rvalue引用。
编辑(后续问题):
您不能使用Scale
重载f(T)
来实现您想要的效果。虽然只有前者将用于rvalues,但是左值可以同样很好地绑定到任一参数,因此使用左值调用f(T&)
是不明确的,并导致编译时错误。
但是,f
的重载有什么问题:
DiagonalView
有什么我想念的吗?
另一个修改:
然后我需要一个非常大量的重载,因为目前有超过5个视图,并且有几十个函数,如Scale。
然后,您需要将可以以相同方式处理的那些类型组合在一起。您可以使用一些简单的模板元素来进行分组。在我的头顶:
template <class T>
void Scale(MatrixBase<T> &matrix, const T &scale_factor);
template <class T>
void Scale(DiagonalView<T> view, const T &scale_factor);
这种特殊的设置/分组可能并不完全符合您的需求,但您可以通过适合自己的方式设置此类设置。
答案 1 :(得分:1)
解决此问题的一种简单方法是使用boost::shared_ptr< MatrixBase<T> >
而不是引用。
答案 2 :(得分:0)
可能是,您应该使用const
。 ?
template <class T>
void Scale(const MatrixBase<T> &A, const T &scale_factor);
答案 3 :(得分:0)
你是限制Scale的第一个参数的类型,但你可以让编译器自己找出适合的类型,如下所示:
template <class M,class T>
void Scale(M A, const T &scale_factor);
答案 4 :(得分:0)
不要使用引用,按值传递。
如果需要,让copy elision为您进行优化。