OK、私はあまりにも長い間グーグルで検索してきましたが、この手法を何と呼ぶべきかわからないので、SOでここで質問する方がよいと考えました. これに明らかな名前や見落としている解決策がある場合は、正しい方向に向けてください。
素人向け:テンソルは行列の論理拡張であり、行列がベクトルの論理拡張であるのと同じです。ベクトルはランク 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[]
私は次の不完全な「解決策」を思いつきました。このメソッドは、ベクトル/行列 (ランク 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;
}
};
しかし、これはちょうど間違っているように感じます...
上で概説したインデックス作成/代入メソッドごとに、個別のTensor<T>::Sass::Sass(...)
コンストラクター、 new Tensor<T>::Sass::operator=(...)
、およびそのTensor<T>::operator()(...)
ような操作ごとに new を定義する必要があります。さらに、 にはTensor<T>::Sass::operators=(...)
、対応する にすでにあるものと同じものの多くを含める必要があり、すべてを任意のランクTensor<T>::operator()(...)
の に適したものにすることで、このアプローチは非常に醜く、冗長になりすぎ、さらに重要なことに、完全に管理できなくなります。Tensor<>
ですから、私は、これらすべてに対してもっと効果的なアプローチがあるという印象を受けています。
助言がありますか?