对于外行人来说: tensor 是矩阵的逻辑扩展,就像矩阵是向量的逻辑扩展一样。向量是秩-1张量(在编程术语中,1D数字数组),矩阵是秩-2张量(数字的2D数组),而秩-N张量则是数字的ND数组。
现在,假设我有类似Tensor类的东西:
template<typename T = double> // possibly also with size parameters
class Tensor
{
private:
T *M; // Tensor data (C-array)
// alternatively, std::vector<T> *M
// or std::array<T> *M
// etc., or possibly their constant-sized versions
// using Tensor<>'s template parameters
public:
... // insert trivial fluffy stuff here
// read elements
const T & operator() (size_t a, size_t b) const {
... // error checks etc.
return M[a + rows*b];
}
// write elements
T & operator() (size_t a, size_t b) {
... // error checks etc.
return M[a + rows*b];
}
...
};
使用operator()(...)
的这些定义,索引/分配单个元素则具有相同的调用签名:
Tensor<> B(5,5);
double a = B(3,4); // operator() (size_t,size_t) used to both GET elements
B(3,4) = 5.5; // and SET elements
将其扩展到任意张量等级是相当简单的。但我希望能够实现的是一种更高级的索引/分配元素的方法:
Tensor<> B(5,5);
Tensor<> C = B( Slice(0,4,2), 2 ); // operator() (Slice(),size_t) used to GET elements
B( Slice(0,4,2), 2 ) = C; // and SET elements
// (C is another tensor of the correct dimensions)
我知道std::valarray
(以及其他许多人)已经做了一件非常相似的事情,但是完成行为不是我的目标;我的目标是学习如何优雅,高效和安全将以下功能添加到我的Tensor<>
课程中:
// Indexing/assigning with Tensor<bool>
B( B>0 ) += 1.0;
// Indexing/assigning arbitrary amount of dimensions, each dimension indexed
// with either Tensor<bool>, size_t, Tensor<size_t>, or Slice()
B( Slice(0,2,FINAL), 3, Slice(0,3,FINAL), 4 ) = C;
// double indexing/assignment operation
B(3, Slice(0,4,FINAL))(mask) = C; // [mask] == Tensor<bool>
.. etc.
请注意,我打算将operator[]
用于operator()
的未检查版本。或者,我会更加坚持使用std::vector<>
方法对.at()
的已检查版本使用operator[]
方法。无论如何,这是一个设计选择,除了现在的问题。
我想到了以下不完整的“解决方案”。这种方法只能用于矢量/矩阵(rank-1或rank-2张量),并且有许多不良的副作用:
// define a simple slice class
Slice ()
{
private:
size_t
start, stride, end;
public:
Slice(size_t s, size_t e) : start(s), stride(1), end(e) {}
Slice(size_t s, size_t S, size_t e) : start(s), stride(S), end(e) {}
...
};
template<typename T = double>
class Tensor
{
... // same as before
public:
// define two operators() for use with slices:
// version for retrieving data
const Tensor<T> & operator() (Slice r, size_t c) const {
// use slicing logic to construct return tensor
...
return M;
{
// version for assigning data
Sass operator() (Slice r, size_t c) {
// returns Sass object, defined below
return Sass(*this, r,c);
}
protected:
class Sass
{
friend class Tensor<T>;
private:
Tensor<T>& M;
const Slice &R;
const size_t c;
public:
Sass(Tensor<T> &M, const Slice &R, const size_t c)
: M(M)
, R(R)
, c(c)
{}
operator Tensor<T>() const { return M; }
Tensor<T> & operator= (const Tensor<T> &M2) {
// use R/c to copy contents of M2 into M using the same
// Slice-logic as in "Tensor<T>::operator()(...) const" above
...
return M;
}
};
但这感觉不对......
对于上面列出的每种索引/分配方法,我必须为每个此类构建器定义一个单独的Tensor<T>::Sass::Sass(...)
构造函数,一个新的Tensor<T>::Sass::operator=(...)
和一个新的Tensor<T>::operator()(...)
操作。此外,Tensor<T>::Sass::operators=(...)
需要包含相应Tensor<T>::operator()(...)
中已有的大部分相同内容,并使所有内容适合任意等级的Tensor<>
这种方法相当丑陋,过于冗长,更重要的是,完全无法管理。
所以,我认为对所有这些都有更有效的方法。
有什么建议吗?
答案 0 :(得分:3)
首先,我想指出一些设计问题:
T & operator() (size_t a, size_t b) const;
建议您不能通过此方法更改矩阵,因为它是const
。但是你给了一个矩阵元素的nonconst引用,所以实际上你可以改变它。这只是因为你正在使用的原始指针而编译。我建议改用std::vector
,它会为你做内存管理,并会给你一个错误,因为vector {const} operator[]
的const版本给出了一个像它应该的const引用。
关于你的实际问题,我不确定Slice构造函数的参数应该做什么,也不确定Sass对象是什么意思(我不是母语,而“Sass”只给了我一个字典翻译,意思是......像“无礼”,“无礼”。 但是,我想用切片创建一个对象,可以访问由切片参数定义的矩阵子集。
我建议不要使用operator()
来访问矩阵。具有两个索引来访问给定元素的op()似乎很自然。使用类似的运算符来获取整个矩阵似乎不太直观。
这是一个想法:创建一个Slice类,其中包含对Matrix的引用以及定义Matrix的哪个部分由Slice表示的必要参数。这样,Slice就像它定义的Matrix子集的代理,类似于一对迭代器,可以看作是它们指向的容器子范围的代理。为您的Matrix提供一对slice()
方法(const和nonconst),这些方法返回Slice / ConstSlice,引用您调用方法的Matrix。这样,您甚至可以将检查放入方法中,以查看Slice的参数是否对其引用的Matrix有意义。如果它有意义并且是必要的,您还可以添加转换运算符,将Slice转换为自己的Matrix。
一次又一次地重载operator()并使用参数作为掩码,因为线性索引和其他东西比帮助imo更令人困惑。 operator()
是光滑的,如果它做了一些自然的东西,每个人都希望它。如果代码在任何地方使用,它只会混淆代码。改为使用命名方法。
答案 1 :(得分:0)
不是答案,只是跟进我的评论的注意事项:
Tensor<bool> T(false);
// T (whatever its rank) contains all false
auto lazy = T(Slice(0,4,2));
// if I use lazy here, it will be all false
T = true;
// now T contains all true
// if I use lazy here, it will be all true
这个可能是您想要的,或者可能是意料之外的。
一般来说,这可以使用不可变张量干净地工作,但是允许变异会产生与COW字符串相同的问题。
答案 2 :(得分:0)
如果你允许你的Tensor隐含为double,那么你只能从operator()重载中返回Tensors。
operator double() {
return M.size() == 1 ? M[0] : std::numeric_limits<double>::quiet_NaN();
};
应该允许
double a = B(3,4);
Tensor<> a = B(Slice(1,2,3),4);
要让operator()使用Slice和integer来处理多个重载是另一个问题。我可能只是使用Slice并创建另一个隐式转换,因此整数可以是Slice,然后可能使用变量参数elipses。
const Tensor<T> & operator() (int numOfDimensions, ...)
虽然变量参数路由是最好的,只有8个特殊化的Slice 1-8参数。