我目前有一个类似的类层次结构
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);
顾名思义,它做了显而易见的事情。我希望能够传入一个诚实至善的非 View 矩阵,或者 MatrixView
的子类实例。 .上面写的原型(prototype)不适用于
Scale(Diagonal(A), 2.0);
因为 DiagonalView
Diagonal
返回的对象是临时的,Scale
采用非常量引用,不能接受临时引用。有什么办法可以使这项工作?我尝试使用 SFINAE,但我不太了解它,而且我不确定这是否能解决问题。对我来说重要的是可以在不提供显式模板参数列表的情况下调用这些模板化函数(我想要隐式实例化)。理想情况下,上面的语句可以按书面形式工作。
编辑:(跟进问题)
正如 sbi 在下面对右值引用和临时对象的回应,有没有办法定义两个版本的 Scale,一个对非 View 采用非 const 右值引用,另一个采用按值传递 View ?问题是在编译时以隐式实例化起作用的方式区分这两者。
更新
我已经将类层次结构更改为
ReadableMatrix
WritableMatrix : public ReadableMatrix
WritableMatrixView
DenseMatrix : public WritableMatrix
DiagonalView : public WritableMatrixView
原因WritableMatrixView
不同于WritableMatrix
是 View 必须通过 const 引用传递,而矩阵本身必须通过非常量引用传递,因此访问器成员函数具有不同的 const-ness。现在像 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 View ,一个用于实际矩阵的非 const 版本。这意味着像 Mult(A, B, C)
这样的函数,我将需要 8 个重载,但至少它可以工作。然而,不起作用的是在其他功能中使用这些功能。你看,每个 View
-like 类包含一个成员 View
它在看什么;例如在表达式 Diagonal(SubMatrix(A))
中, Diagonal
函数返回 DiagonalView<SubMatrixView<T> >
类型的对象,它需要知道 A
的完全派生类型.现在,假设在 Scale
内我调用了一些类似的其他函数,它采用基本 View 或矩阵引用。那会失败,因为构建了所需的 View
需要 Scale 参数的派生类型;它没有的信息。仍在努力寻找解决方案。
更新
我使用了实际上是 Boost 的 enable_if 的本土版本来在函数的两个不同版本之间进行选择,例如 Scale
.它归结为用额外的 typedef 标记标记我所有的矩阵和 View 类,指示它们是可读和可写以及 View 还是非 View 。最后,我仍然需要 2^N 重载,但现在 N 只是非常量参数的数量。有关最终结果,请参阅 here (不太可能再次进行认真改造)。
最佳答案
这与模板无关。你的例子
Scale(Diagonal(A), 2.0);
可以概括为
f(g(v),c);
在 C++03 中,这要求 f()
的第一个参数按拷贝或按 const
引用传递。原因是 g()
返回一个临时的右值。但是,右值仅绑定(bind)到 const
引用,而不绑定(bind)到非常量引用。这与是否涉及模板、SFINAE、TMP 或诸如此类的东西无关。这只是语言(当前)的方式。
这背后还有一个基本原理:如果 g()
返回一个临时文件,而 f()
修改那个临时文件,那么没有人有机会“看到”临时修改。因此修改是徒劳的,整个事情很可能是一个错误。
据我了解,在你的情况下,g()
的结果是一个临时的,它是对其他对象 (v
) 的 View ,因此修改它将修改 v
。但如果是这种情况,在当前的 C++ 中,g()
的结果必须是 const
(以便它可以绑定(bind)到 const
引用,否则它必须被复制。因为 const
对我来说“闻起来”不对,所以最好不要复制该 View 。
然而,还有更多。 C++1x 将引入所谓的右值引用。我们所知道的“引用”将分为左值引用或右值引用。您将能够让函数采用右值引用,甚至基于“l/rvalue-ness”进行重载。这被认为是允许类设计者为右值右手边重载复制构造函数和赋值,并让它们“窃取”右手边的值,这样复制右值会更便宜。但是您可能会使用它来让 Scale
获取一个右值并对其进行修改。
不幸的是,您的编译器很可能还不支持右值引用。
编辑(跟进问题):
你不能用 f(T&)
重载 f(T)
来实现你想要的。虽然只有前者将用于右值,但左值可以同样很好地绑定(bind)到任一参数,因此使用左值调用 f
是不明确的,并会导致编译时错误。
但是,重载 DiagonalView
有什么问题:
template <class T>
void Scale(MatrixBase<T> &matrix, const T &scale_factor);
template <class T>
void Scale(DiagonalView<T> view, const T &scale_factor);
有什么我想念的吗?
另一个编辑:
I would need a ridiculously large number of overloads then, since there are currently more than 5 views, and there are several dozen functions like Scale.
然后您需要将那些可以用相同方式处理的类型组合在一起。您可以使用一些简单的模板元数据来进行分组。在我的脑海中:
template<bool B>
struct boolean { enum { result = B }; };
template< typename T >
class some_matrix {
public:
typedef boolean<false> is_view;
// ...
};
template< typename T >
class some_view {
public:
typedef boolean<true> is_view;
// ...
};
namespace detail {
template< template<typename> class Matrix, typename T >
void Scale(Matrix<T>& matrix, const T& scale_factor, boolean<true>)
{
/* scaling a matrix*/
}
template< template<typename> class Matrix, typename T >
void Scale(View<T>& matrix, const T& scale_factor, boolean<true>)
{
/* scaling a view */
}
}
template< template<typename> class Matrix, typename T >
inline void Scale(Matrix<T>& matrix, const T& scale_factor)
{
detail::Scale( matrix, scale_factor, typename Matrix<T>::is_view() );
}
这种特定的设置/分组可能并不完全符合您的需要,但您可以按照适合自己的方式进行设置。
关于C++ 隐式模板实例化,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1741840/