Skip to content

Instantly share code, notes, and snippets.

@SteveBronder
Created June 12, 2020 17:20
Show Gist options
  • Select an option

  • Save SteveBronder/84c710c3f355eb53aa946983e48289a9 to your computer and use it in GitHub Desktop.

Select an option

Save SteveBronder/84c710c3f355eb53aa946983e48289a9 to your computer and use it in GitHub Desktop.

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);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment