Fortran コードをいつの日か C++ に簡単に移植できるようにすることを期待して、配列全体の算術演算子と、長い配列の指定されたセクションからコピーして代入する機能を提供する式テンプレート コードに取り組んできました。残念ながら、できる限り削減する定型コードのかなりの部分がなければ、私の質問に到達する方法は考えられません。
まず最初に、ポインターと長さをカプセル化した非常に単純な「C スタイルの配列構造体」を用意しました。これは、混合言語アプリケーションの C、Fortran、C++、および Java 部分の間で簡単にやり取りするのに適しています。
typedef struct {
int *p; /*!< Pointer to the data */
int n; /*!< The number of elements; int, not size_t, for Fortran compatibility */
} int_array_C;
typedef struct {
float *p; /*!< Pointer to the data */
int n; /*!< The number of elements; int, not size_t, for Fortran compatibility */
} float_array_C;
typedef struct {
double *p; /*!< Pointer to the data */
int n; /*!< The number of elements; int, not size_t, for Fortran compatibility */
} double_array_C;
...すべてのネイティブ タイプについても同様です。次に、ウィキペディアのその主題に関するエントリで提案されているアプローチに基づいて、いくつかの非常に単純な式テンプレートを定義します。
template <typename E, typename T_C >
class VecExpression
{
typedef typename std::remove_pointer<decltype(T_C::p)>::type TT;
public:
//! Returns a const reference to the i'th element in the array
TT operator[] (int i) const noexcept
{
return static_cast<E const&>(*this)[i];
}
//! Returns the total size of the array
int size() const noexcept
{
return static_cast<E const &>(*this).size();
}
operator E&() { return static_cast<E&>(*this); }
operator E const&() const { return static_cast<const E&>(*this); }
};
template <typename E1, typename T_C, typename E2, typename U_C >
class VecSum : public VecExpression< VecSum<E1, T_C, E2, U_C>, T_C >
{
E1 const & _u;
E2 const & _v;
public:
//! Constructor taking two VecExpressions
VecSum(VecExpression<E1, T_C> const& u, VecExpression<E2, U_C> const &v) : _u(u), _v(v)
{
assert(u.size() == v.size());
}
int size() const noexcept { return _v.size(); }
auto operator[](int i) const
-> const decltype(_u[i] + _v[i]) { return _u[i] + _v[i]; }
// Automatically takes care of type promotion e.g. int to double
// according to the compiler's normal rules
};
template <typename E1, typename T_C, typename E2, typename U_C >
VecSum<E1, T_C, E2, U_C> const operator+(VecExpression<E1, T_C> const &u,
VecExpression<E2, U_C> const &v)
{
return VecSum<E1, T_C, E2, U_C>(u, v);
}
C スタイルのベクトルの内容を操作する方法を提供するために、いくつかのテンプレートを定義します。1 つは既存のバッファー内のデータを操作するもので、もう 1 つは std::vector を使用して独自のメモリを管理するものです。
template <typename T_C> class nArray : public T_C, public VecExpression<nArray <T_C>, T_C >
{ // This is the 'curiously recurring template
// pattern' (CRTP)
typedef typename std::remove_pointer<decltype(T_C::p)>::type TT;
struct startingIndex : public T_C
{
size_t start;
startingIndex(const T_C *initialiser) noexcept
{
*(static_cast<T_C *>(this)) = *initialiser;
}
nArray to(int element) noexcept
{
T_C::n = element - start + 1;
nArray<T_C> newArray(*(static_cast<T_C *>(this)));
return newArray;
}
};
public:
//! Constructor to create an nArray from an array_C, without copying its memory
nArray(T_C theArray) noexcept
{
T_C::p = theArray.p;
T_C::n = theArray.n;
}
//! Constructor to create an nArray from an ordinary C array, without copying its memory
template<std::size_t N>
nArray(TT (&theArray)[N]) noexcept
{
T_C::p = &theArray[0];
T_C::n = N;
}
nArray & operator=(VecExpression<nArray<T_C>, T_C> const& source) &
{
// Note that we cannot use the copy-and-swap idiom here because we don't have the means to
// construct a new temporary memory buffer. Therefore we have to handle the assignment-to-self
// case explicitly.
if (&source == this) return *this;
assert(T_C::n == source.size());
for (int i=0; i<T_C::n; ++i) T_C::p[i] = source[i];
return *this;
}
//! Copy assignment operator taking a VecExpression of a different (but compatible) type
//! without allocating any new memory
template <typename E, typename U_C>
nArray operator=(VecExpression<E, U_C> const& source) &
{
assert(T_C::n == source.size());
for (int i=0; i<T_C::n; ++i) T_C::p[i] = static_cast<TT>(source[i]);
return *this;
}
//! Returns a non-const reference to the i'th element in the array
TT& operator[] (int i) noexcept
{
return T_C::p[i];
}
//! Returns a const reference to the i'th element in the array
const TT& operator[] (int i) const noexcept
{
return T_C::p[i];
}
startingIndex from(int element) const noexcept
{
startingIndex theNewArray(this);
theNewArray.p = &T_C::p[static_cast<size_t>(element)];
theNewArray.n = T_C::n - element;
theNewArray.start = element;
return theNewArray;
}
nArray to(int element) const noexcept
{
nArray theNewArray;
theNewArray.p = T_C::p;
theNewArray.n = element + 1;
return theNewArray;
}
// ... and a whole bunch of other functions
};
template <typename T_C> class nVector : public nArray<T_C>
{
typedef typename std::remove_pointer<decltype(T_C::p)>::type TT;
public:
template<std::size_t N>
nVector(TT (&source)[N])
{
contents.resize(N);
update_basetype();
std::copy(&source[0], &source[N], contents.begin());
}
// ...and a whole bunch of other constructors and assignment operators
// which echo those of nArray with the additional step of resizing the
// internal std::vector and copying the contents into it
private:
void update_basetype() noexcept
{
T_C::p = contents.size() > 0 ? contents.data() : nullptr;
T_C::n = contents.size();
}
std::vector<TT> contents;
};
typedef nArray<float_array_C> float_array;
typedef nVector<float_array_C> float_vector;
// ...and so on
ふぅ!この時点から、次のようなことができます
float a[] = { 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f };
float b[] = { 9.0f, 8.0f, 7.0f, 6.0f, 5.0f, 4.0f };
float_array aArray(a); // The array contents aren't copied, only
float_array bArray(b); // the pointers
float_vector aVector = aArray.from(2); // aVector is { 3.0f, 4.0f, 5.0f, 6.0f }
float_vector bVector = bArray.to(3); // bVector is { 9.0f, 8.0f, 7.0f, 6.0f }
float_vector cVector = aArray.from(2).to(4) + bArray.from(1).to(3);
// cVector is { 11.0f, 11.0f, 11.0f }
...そして、彼らは御馳走を働かせます。さて、やっと私の質問に行くことができます。たとえば、配列サブセクションに割り当てたいとします。
float_vector dVector(10); // An empty 10-element array
dVector.from(3).to(5) = aArray.from(2).to(4) + bArray.from(1).to(3);
実際のところ、Visual C++ 2013 でコンパイルすると問題なく動作しますが、gcc では動作しません。割り当てでコンパイルが失敗し、次のメッセージが表示されます。
error: no match for 'operator=' (operand types are 'nArray<float_array_C>' and 'const VecSum<nArray<float_array_C>, float_array_C, nArray<float_array_C>, float_array_C>')
note: candidates are:
< ...skipping over a long list of utterly implausible options>
note: nArray<T_C>& nArray<T_C>::operator=(const VecExpression<nArray<T_C>, T_C>&) & [with T_C = float_array_C]
note: no known conversion for implicit 'this' parameter form 'nArray<float_array_C>' to 'nArray<float_array_C>&'
現在、このエラー メッセージは、一時オブジェクトを非 const 参照に代入しようとしたとき、または Rvalue に代入しようとしたときに文献に表示されるようであり、Visual C++ はこの規則に関して怠け者であると文書化されています。 gcc よりも優れています (当然、gcc は標準に準拠したものです)。コンパイラがなぜ
dVector.from(3).to(5)
Rvalue として、そうならないように後ろ向きに曲げましたが。たとえば、私の startingIndex::to() メソッドは、参照ではなく値によって nArray オブジェクトを熱心に返します。
auto test1 = dVector.from(3).to(5);
auto test2 = aArray.from(2).to(4) + bArray.from(1).to(3);
test1 = test2;
...その後、これは正常に機能し、コンパイラは「test1」が「nArray<float_array_C>」(つまり、float_array) であることを正確に伝えます。
だから、私の質問は: 私は実際にここで Rvalue に代入しようとしているのですか? もしそうなら、どうすればそれをやめることができますか? これが何らかの方法で C++ で実行できることを心から願っています。それ以外の場合は、Fortran ランドに戻って、
dVector(3:5) = aArray(2:4) + bArray(1:3)
……これからも幸せに暮らします。
アップデート
Chris Dodd の提案に従って、さらに nArray コンストラクターのいくつかの異なる形式を試し、次のように落ち着きました。
nArray && operator=(VecExpression<nArray<T_C>, T_C> const& source) &&
{
if (&source == this) return *this;
assert(T_C::n == source.size());
for (int i=0; i<T_C::n; ++i) T_C::p[i] = source[i];
return *this;
}
(当分の間、自分自身への割り当てチェックを強化する必要はありません)。gcc コンパイラーはこれを乗り越えたように見えましたが、次のエラーは次のとおりでした。
no known conversion for argument 1 from 'const VecSum<nArray<float_array_C>, float_array_C, nArray<float_array_C>, float_array_C>' to 'const VecExpression<nArray<float_array_C>, float_array_C>&'
これは少なくとも別のメッセージ (常に進歩のしるし) ですが、基本的な問題は間違った種類の参照への代入であることを示唆しています。残念ながら、これをどこで探すべきかわかりません: operator+ が値による戻りではなく VecSum<E1, T_C, E2, U_C> const & を返すように実験しましたが、これはまったく違いがありませんでした. 今のところまた行き詰まっているので、次の戦略は、clang を Linux パーティションにインストールして、より役立つエラー メッセージが表示されるかどうかを確認することです...
さらに更新:
Clang は特に役に立ちませんでした。それが言うことができたのは次のことだけでした:
candidate function not viable: no known conversion from 'nArray<[...]>' to 'nArray<[...]>' for object argument
これは多くの手がかりを与えません!
最終更新:
振り返ってみると、解決策がどれほど明白であったかについて、私は実際には非常に恥ずかしいです。私がする必要があったのは、代入演算子に、通常の移動代入演算子とまったく同じ形式を与えることだけでした。
nArray & operator=(VecExpression<nArray<T_C>, T_C> const& source) &&
{
// Better assignment-to-self check pending...
assert(T_C::n == source.size());
for (int i=0; i<T_C::n; ++i) T_C::p[i] = source[i];
return *this;
}
もちろん、これは最初に Chris Dodd が提案したとおりであり、Linux と Windows の clang と gcc でまったく問題なく動作します。