C ++隐式模板实例化

时间:2009-11-16 12:19:05

标签: c++ templates metaprogramming

我目前有一个类层次结构,如

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

WritableMatrixViewWritableMatrix不同的原因是视图必须由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(不太可能再次对其进行认真修改)。

5 个答案:

答案 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为您进行优化。