C++ 如何使用相同的调用签名索引和分配张量中的元素?
好吧,我在谷歌上搜索了太久了,我只是不知道该怎么称呼这种技术,所以我想最好还是在这里问一下。如果这有一个明显的名称和/或我忽略的解决方案,请为我指出正确的方向 对于外行来说:张量是矩阵的逻辑延伸,就像矩阵是向量的逻辑延伸一样。向量是秩-1张量(在编程术语中,是一维数字数组),矩阵是秩-2张量(二维数字数组),秩-N张量就是N-D数字数组 现在,假设我有这样的张量类:C++ 如何使用相同的调用签名索引和分配张量中的元素?,c++,arrays,vector,matrix,variable-assignment,C++,Arrays,Vector,Matrix,Variable Assignment,好吧,我在谷歌上搜索了太久了,我只是不知道该怎么称呼这种技术,所以我想最好还是在这里问一下。如果这有一个明显的名称和/或我忽略的解决方案,请为我指出正确的方向 对于外行来说:张量是矩阵的逻辑延伸,就像矩阵是向量的逻辑延伸一样。向量是秩-1张量(在编程术语中,是一维数字数组),矩阵是秩-2张量(二维数字数组),秩-N张量就是N-D数字数组 现在,假设我有这样的张量类: template<typename T = double> // possibly also with size pa
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];
}
...
};
将其扩展到任意张量秩是相当简单的。但我希望能够实现一种更高级的索引/分配元素的方法:
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)
请注意,我打算对operator()
的未检查版本使用operator[]
。或者,我将更多地使用std::vector
方法,使用.at()
方法检查操作符[]
的版本。无论如何,这是一个设计选择,除了现在的问题
我想出了以下不完整的“解决方案”。该方法仅适用于向量/矩阵(秩-1或秩-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;
}
};
//定义一个简单的切片类
切片()
{
私人:
尺寸
开始、跨步、结束;
公众:
切片(大小s,大小e):开始(s),跨步(1),结束(e){}
切片(大小s,大小s,大小e):开始(s),跨步(s),结束(e){}
...
};
模板
类张量
{
…//和以前一样
公众:
//定义两个用于切片的运算符():
//用于检索数据的版本
常量张量和运算符()(切片r,大小c)常量{
//利用切片逻辑构造返回张量
...
返回M;
{
//用于分配数据的版本
Sass运算符()(切片r,大小\u t c){
//返回下面定义的Sass对象
返回Sass(*本,r,c);
}
受保护的:
类Sass
{
友元类张量;
私人:
张量&M;
常数切片&R;
const size_t c;
公众:
Sass(张量和M、常数切片和R、常数大小c)
:M(M)
,R(R)
,c(c)
{}
运算符张量()常量{return M;}
张量和运算符=(常数张量和M2){
//使用R/c将M2的内容复制到M中
//切片逻辑如上面的“Tensor::operator()(…)const”所示
...
返回M;
}
};
但这感觉不对
对于上面列出的每个索引/赋值方法,我必须为每个这样的操作定义一个单独的Tensor::Sass::Sass(…)
构造函数,一个新的Tensor::Sass::operator=(…)
,以及一个新的Tensor::operator()(…)
需要包含与相应的Tensor::operator()(…)
中已经存在的内容相同的内容,并且使所有内容都适合于任意秩的Tensor
,这使得这种方法非常丑陋,过于冗长,更重要的是,完全无法管理
因此,我觉得有一种更有效的方法来解决所有这些问题
有什么建议吗?首先,我想指出一些设计问题:
T & operator() (size_t a, size_t b) const;
建议您不能通过此方法更改矩阵,因为它是const
。但您正在返回对矩阵元素的非ST引用,因此实际上您可以更改它。这只会编译,因为您使用的是原始指针。我建议改为使用std::vector
,它会为您进行内存管理,并将给您一个错误,因为vector的常量版本的操作符[]
提供了一个常量引用
关于您的实际问题,我不确定切片构造函数的参数应该做什么,也不确定Sass对象是什么(我不是母语人士,“Sass”在字典中只给了我一个翻译,意思是“厚颜无耻”、“无礼”)。
但是,我假设您希望使用切片创建一个对象,该对象允许访问由切片参数定义的矩阵子集
我建议不要使用operator()
来访问矩阵。使用两个索引的op()访问给定元素似乎很自然。对我来说,使用类似的运算符来获得整个矩阵似乎不那么直观
这里有一个想法:创建一个Slice类,该类包含对矩阵的引用和必要的参数,这些参数定义了矩阵的哪个部分由Slice表示。这样一来,Slice就像是它定义的矩阵子集的代理,类似于一对迭代器,它可以被看作是容器子范围的代理指向。为矩阵提供一对slice()
方法(常量和非常量)这将返回一个Slice/ConstSlice,引用调用该方法的矩阵。这样,您甚至可以检查该方法,以查看Slice的参数对它所引用的矩阵是否有意义。如果有意义且必要,您还可以添加一个转换运算符,将一个Slice转换为它自己的矩阵
一次又一次地重载操作符()并将参数用作掩码、线性索引和其他内容比帮助imo更容易混淆。
operator()
如果它做了一些大家都期望它做的自然的事情,那么它是很流畅的。如果它在任何地方都使用,它只会混淆代码。请使用命名方法。不是答案,只是一个注释来跟进我的评论:
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
张量T(假);
//T(无论其级别如何)包含所有false
自动延迟=T(切片(0,4,2));
//如果我在这里用lazy,那就全错了
T=真;
//
T & operator() (size_t a, size_t b) const;
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
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);
const Tensor<T> & operator() (int numOfDimensions, ...)