使用犰狳库进行意外(错误)的模板推导
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;
}
为了说明,我定义了两个函数 sq
和 sqgen
,它们基本上完成相同的工作。 sq
是模板参数MatrixClass
为arma::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()));
我正在编写一个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;
}
为了说明,我定义了两个函数 sq
和 sqgen
,它们基本上完成相同的工作。 sq
是模板参数MatrixClass
为arma::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()));