【问题标题】:Unexpected (wrong) template deduction using armadillo library使用犰狳库的意外(错误)模板推导
【发布时间】:2017-12-05 18:46:21
【问题描述】:

我正在编写一个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,它们基本上做同样的工作。当模板参数MatrixClassarma::Mat&lt;double&gt; 时,sqsqgen 的显式实例化。编译与

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)));

代码编译正确。

【问题讨论】:

    标签: c++ templates armadillo


    【解决方案1】:

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

    重载分辨率匹配精确的类型、父类型和可转换的类型。

    奇怪的是arma::Mat 的操作会生成表达式模板,这些模板可以转换为矩阵,但它们本身不是矩阵。它们的存在是为了让您可以进行一整行矩阵数学运算,并且在您将所有内容实际转换为矩阵之前有效地不要这样做。

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

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

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

    所以,试试

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

    【讨论】:

      【解决方案2】:

      编译器工作正常。 Armadillo 对涉及矩阵的表达式进行了大量优化,例如重新排序乘法、延迟评估等。这一切都是通过模板元编程完成的。 Armadillo 矩阵类提供了复制赋值运算符(例如,mat::operator=()),它采用其他矩阵并复制它们的数据。但是,operator=() 没有重载,它采用这些表达式模板之一。因此出现关于operand types are 'eGlue&lt;...&gt;...' 等的错误。

      对此的快速解决方法是将.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;
      

      【讨论】:

      • 我怀疑这些会起作用,因为sqgen 返回一个表达式模板,其值由过期的本地矩阵确定似乎不太可能起作用。
      • @Yakk 对,不错。你的回答更合适。
      • 建议的解决方案均无效。即使添加 eval() 编译器仍然会抱怨 error: no match for ‘operator=’ (operand types are ‘arma::eGlue&lt;arma::Mat&lt;double&gt;, arma::Mat&lt;double&gt;, arma::eglue_minus&gt;’ and ‘arma::Mat&lt;double&gt;’)。使用 operator*= one 会产生类似的错误error: no match for ‘operator*=’ (operand types are ‘arma::eGlue&lt;arma::Mat&lt;double&gt;, arma::Mat&lt;double&gt;, arma::eglue_minus&gt;’ and ‘const arma::eGlue&lt;arma::Mat&lt;double&gt;, arma::Mat&lt;double&gt;, arma::eglue_minus&gt;’)
      • @francesco 我尝试了 Yakk 的解决方案,它奏效了。您确定eval() 在正确的位置吗?应该是sqgen((id - m).eval())。请注意 (id - m) 周围的额外括号,这会强制对表达式 传递给 sqgen() 之前进行求值。
      • @bnaecker 我的意思是,您提出的解决方案不起作用。 Yakk 的解决方案确实有效。无论如何,感谢您的努力。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多