使用犰狳库进行意外(错误)的模板推导

Unexpected (wrong) template deduction using armadillo library

我正在编写一个c++模板函数,旨在计算一个矩阵函数,其中矩阵类型是模板参数。将它与犰狳库一起使用时,编译出现意外失败。 我正在使用犰狳 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,它们基本上完成相同的工作。 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 在上面的代码中应该正好等于 sq,因此我推测编译器没有正确推导出 sqgen(id - m) 中的模板参数 MatrixClass。 事实上,在替换

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

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

代码编译正确。

编译器工作正常。 Armadillo 对涉及矩阵的表达式进行大量优化,例如重新排序乘法、延迟求值等。这一切都是通过模板元编程完成的。 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;

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

重载解析匹配精确类型、父类型和可转换为的类型。

很可能 arma::Mat 的操作会生成可以转换为矩阵的表达式模板,但它们本身并不是矩阵。它们的存在使您可以采用整行矩阵数学运算,并且在您真正将所有内容转换为矩阵之前有效地不这样做。

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

然后创建一个没有参数的临时表达式模板实例,将其与另一个表达式模板相乘,分配给它,然后 return 它。 None 这些对表达式模板有意义。

这是表达式模板和通用代码的一个已知问题。通常有一些方法可以强制计算表达式模板。将它们分配到一个矩阵就可以了(sq 是如何工作的),转换它们是有效的,在这种情况下,有一个 .eval() 成员函数可以做到这一点而不必命名类型。

所以,试试

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