2017-12-05 5 views
1

私は行列の関数を計算するためのC++テンプレート関数を書いています。行列の型はテンプレートパラメータです。 armadilloライブラリで使用すると、予期せぬコンパイルエラーが発生します。 私はarmadillo 8.300とgcc 7.2.0を使用しています。 以下では、問題を説明するテストプログラムです。 アルマジロライブラリを使用した、予期しない(間違った)テンプレートの控除

#include <armadillo> 

arma::Mat<double> sq(const arma::Mat<double>& M) 
{ 
    arma::Mat<double> res(M); 
    res = res * M; 
    return res; 
} 

template <class MatrixClass> 
MatrixClass sqgen(const MatrixClass& M) 
{ 
    MatrixClass res(M); 
    res = res * M; 
    return res; 
} 

int main() 
{ 
    arma::Mat<double> id(3, 3, arma::fill::eye); 
    arma::Mat<double> m(3, 3, arma::fill::ones); 

    arma::Mat<double> m2(sq(m)); 
    arma::Mat<double> m_id2(sq(id - m)); 

    arma::Mat<double> m2gen(sqgen(m)); 
    arma::Mat<double> m_id2gen(sqgen(id - m)); // Error here 
    return 0; 
} 

は、例示のために私は2つの機能sqと本質的に同じ仕事をsqgenを定義しました。 sqは、テンプレートパラメータMatrixClassarma::Mat<double>の場合の明示的なインスタンス化sqgenです。

g++ -std=c++14 -Wall -pedantic -O3 -o test test.cpp -lstdc++ -larmadillo 

と、コンパイルエラーを与える失敗:

test.cpp: In instantiation of ‘MatrixClass sq(const MatrixClass&) [with MatrixClass = arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>]’: 
test.cpp:24:36: required from here 
test.cpp:14:7: error: no match for ‘operator=’ (operand types are ‘arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>’ and ‘arma::enable_if2<true, const arma::Glue<arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>, arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>, arma::glue_times> >::result {aka const arma::Glue<arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>, arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>, arma::glue_times>}’) 
    res = res * M; 
    ~~~~^~~~~~~~~ 
In file included from /usr/include/armadillo:204:0, 
       from test.cpp:1: 
/usr/include/armadillo_bits/eGlue_bones.hpp:22:7: note: candidate: arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>& arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>::operator=(const arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>&) <deleted> 
class eGlue : public Base<typename T1::elem_type, eGlue<T1, T2, eglue_type> > 
     ^~~~~ 
/usr/include/armadillo_bits/eGlue_bones.hpp:22:7: note: no known conversion for argument 1 from ‘arma::enable_if2<true, const arma::Glue<arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>, arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>, arma::glue_times> >::result {aka const arma::Glue<arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>, arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>, arma::glue_times>}’ to ‘const arma::eGlue<arma::Mat<double>, arma::Mat<double>, arma::eglue_minus>&’ 

問題はsqgenの最後の呼び出しです。 sqgen(m)を呼び出す際に問題はありませんが、sqgen(id - m)という呼び出しでエラーが発生します。代わりに、sq(id - m)というコールを使用することは完全に合法であることに注意してください。上記のコードでは、関数sqgenは正確にに等しいはずなので、私は、コンパイラがテンプレートのパラメータMatrixClasssqgen(id - m)に正しく推定しないと推測します。

arma::Mat<double> m_id2gen(sqgen(arma::Mat<double>(id - m))); 

コードで

arma::Mat<double> m_id2gen(sqgen(id - m)); 

を代入する際に実際に は、正しくコンパイル。

答えて

2

テンプレートパターンマッチングは、正確なタイプ(および正確なタイプが一致しない場合は親タイプのタイプ)と一致します。

オーバーロードの解像度は、変換可能な正確な型、親の型および型に一致します。

奇妙なことに、arma::Matの演算の結果、式テンプレートが生成されます。これは行列に変換できますが、それ自体は行列ではありません。それらは存在しているので、行列の数学の行全体を取り、効率的にすべてを行列に変換するまでそれを実行することはできません。

sqgenは何かをとります。この場合、2つの行列の差を値とする式テンプレートを消費しようとします。

次に、仮の式テンプレートインスタンスを引数なしで作成し、それを別の式テンプレートに掛けて代入して返します。これらはいずれも式テンプレートには意味がありません。

これは、式テンプレートと汎用コードに関する既知の問題です。通常、表現テンプレートの評価を強制する方法があります。それらを行列にアサインすると(そしてsqが機能する)、それらをキャストします。この場合、タイプに名前を付けることなくそれを行う.eval()メンバ関数があります。

ので、値期限切れのローカル行列によって決定された式テンプレートを返すsqgen`が動作する可能性は低いようだ `ので、私は、これらは機能しないだろう

arma::Mat<double> m_id2gen(sqgen((id - m).eval())); 
0

コンパイラは正常に動作しています。アルマジロは、乗算の並べ替え、評価の遅延など、行列に関する表現の大幅な最適化を行います。これはすべてテンプレートメタプログラミングを介して行われます。アルマジロ行列クラスは、他の行列をとり、それらのデータをコピーするコピー代入演算子(例えば、mat::operator=())を提供する。しかし、operator=()にはこれらの表現テンプレートの1つをとる過負荷はありません。したがって、約operand types are 'eGlue<...>...'などのエラー。

これは、式の末尾に.eval()を追加することで、この式の評価が強制的に行われます。だから、やるだろう:

res = (res * M).eval(); 
return res; 

それともを:

return (res * M).eval(); // I'm actually not sure if the eval() is necessary here. 

別のオプションで、場所も正常に動作する必要があります乗算を、やってみることです。次のようになります。

+0

を試してみてください。 – Yakk

+0

@ヤク右、いいキャッチ。あなたの答えはより適切です。 – bnaecker

+0

提案するソリューションはどれも動作しません。 eval()を追加しても、コンパイラは '' 'error: 'operator ='と一致しません(オペランドの型は 'arma :: eGlue 、arma :: Mat 、arma :: eglue_minus>' and 'arma :: Mat ') '' '。オペレータ* = 1を使用すると、同様のエラーが発生します。エラー: 'operator * ='に一致しません(オペランドタイプは 'arma :: eGlue 、arma :: Mat 、arma :: eglue_minus> 'と' const arma :: eGlue 、arma :: Mat 、arma :: eglue_minus> ') '' ' – francesco

関連する問題