Operations in Eigen Expressions hold their arguments either by value or by
reference. Which one is chosen depends on type of the argument. Other
operations are held by value. "Heavy" objects that can hold data themselves,
such as Eigen::Matrix or Eigen::Ref are instead held by reference. This
is the only criterion - holding rvalue arguments by value is not supported,
so we can not use perfect forwarding.
When returning an expression from a function we have to be careful that any arguments in this expression that are held by reference do not go out of scope. For instance, consider the function
template<typename T>
decltype(auto) test_fun(const T& x) {
const Eigen::Ref<const Eigen::VectorXd>& x_ref = x;
return (x_ref.array() - 5.0).matrix();
}
Eigen::MatrixXd test_mat(2,2);
test_mat << 5, 5, 5, 5;
VectorXd X = test_fun(test_mat.diagonal).eval();
std::cout << X;
// Returns {-5, 0}, should be {0, 0}This function will return back a CwiseBinaryOp Eigen expression derived from
subtracting five from x_ref which is then evaluated out of the function
scope before assignment to X. The problem appears when we bring this expression
to the program scope and then attempt to evaluate it. The expression references
x_ref, however x_ref was created from an rvalue eigen expression. This is
a classic dangling pointer problem where a function returning an expression
referencing local matrices or matrices that were rvalue reference arguments
to the function will cause undefined behavior.
A workaround to this issue is allocating and constructing or moving such
objects to the heap. The Holder object is a no-op operation that can also take
pointers to such objects and release them when it goes out of scope. It can
be created either by directly supplying pointers to such objects to holder
function or by forwarding function arguments and moving local variables to
make_holder, which will move any rvalues to heap first. We can fix the above
can by applying the Holder object
template<typename T>
decltype(auto) test_fun(const T& x) {
const Eigen::Ref<const Eigen::VectorXd>& x_ref = x;
return make_holder([](auto& a) { return (a.array() - 5.0).matrix()}, x_ref);
}