使用armadillo库的意外(错误)模板推断

时间:2017-12-05 18:46:21

标签: c++ templates armadillo

我正在编写一个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;
}

为了说明,我定义了两个基本上做同样工作的函数sqsqgen。当模板参数sqsqgen时,MatrixClassarma::Mat<double>的显式实例化。用

编译
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应该在上面的代码中完全等于sq,因此我假设编译器没有正确地推导MatrixClass中的模板参数sqgen(id - m)。 事实上,取代

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

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

代码编译正确。

2 个答案:

答案 0 :(得分:2)

模板模式匹配匹配确切类型(如果确切类型不匹配,则匹配父类型的类型)。

重载分辨率匹配可以转换为的确切类型,父类型和类型。

可能性是arma::Mat的操作导致表达式模板,它们可以转换为矩阵,但不是它们自己的矩阵。它们存在,因此您可以采用整行矩阵数学,并且在您将所有内容实际转换为矩阵之前,不能有效地执行此操作。

因为sqgen接受任何东西,在这种情况下它会尝试使用一个表达式模板,其值是两个矩阵之间的差异。

然后,创建一个不带参数的临时表达式模板实例,将其与另一个表达式模板相乘,赋值给它,然后返回它。这些对表达式模板都没有意义。

这是表达式模板和通用代码的已知问题。通常,有一些方法可以强制评估表达模板。将它们分配给矩阵(它是sq的工作方式),将它们投射起作用,并且在这种情况下,有一个.eval()成员函数可以在不必命名类型的情况下完成它。

所以,试试

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

答案 1 :(得分:0)

编译器工作正常。犰狳对包含矩阵的表达式进行了大量优化,例如重新排序乘法,延迟评估等等。这一切都是通过模板元编程完成的。 Armadillo矩阵类提供复制赋值运算符(例如mat::operator=()),它接受其他矩阵并复制其数据。但是,operator=()没有超载,它会采用其中一个表达式模板。因此关于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.

另一个选择是尝试进行就地乘法,这也应该可以正常工作。如:

res *= M;
return res;