3

The Eigen3 documentation warns against passing Eigen objects by value, but they only refer to objects being used as function arguments.

Suppose I'm using Eigen 3.4.0 and C++20. If I have a struct with an Eigen member, does this mean I can't std::move a pass-by-value in the constructor? Do I need to pass-by-reference and copy the object? Or is this handled somehow by modern move-semantics?

If I can't std::move Eigen objects in a constructor, does this mean I should explicitly delete the move-constructors from my struct?

For example,

#include <utility>
#include <Eigen/Core>

struct Node {
  Eigen::Vector3d position;
  double temperature;

  // is this constructor safe to use?
  Node(Eigen::Vector3d position_, const double temperature_)
    : position(std::move(position_)), temperature(temperature_) {}

  // or must it be this?
  Node(const Eigen::Vector3d& position_, const double temperature_)
    : position(position_), temperature(temperature_) {}

  // also, should move-constructors be explicitly deleted?
  Node(Node&&) = delete;
  Node& operator=(Node&&) = delete;
};
NickFP
  • 177
  • 7
  • 1
    Do you think the constructor argument isn't a function argument (it is), or that it doesn't get copied (it does)? – molbdnilo Jan 20 '22 at 15:53
  • 2
    There is nothing to gain from the first constructor, why would you prefer it? Pass by reference and leave the Eigen copy constructor do its thing. You don't need to do anything with the move semantics, `Vector3d` has fixed allocation, there is nothing to gain from a `std::move` - it can be useful for objects with dynamic allocation – mmomtchev Jan 20 '22 at 15:54

1 Answers1

2

There is nothing magic about Eigen objects. Fixed sized types such as Vector3d behave like std::array. Dynamic sized types like VectorXd behave like std::vector.

Pass-by-value for a dynamic sized type typically is a mistake because it usually invokes a copy construction which can be very expensive for large matrices. Pass-by-reference (const, lvalue, rvalue) is almost always the right choice [footnote 1].

Pass-by-value for fixed-sized types can be a benefit because the first few arguments are usually passed in registers (depending on the platform). This avoids spilling values to the stack. However, this doesn't work for Eigen. I assume they declare a destructor, even if they don't need one. That turns any pass-by-value into pass-by-reference to a hidden copy. You can see this in godbolt. This seems like a missed optimization in Eigen.

In conclusion: Use pass-by-reference. Move-construction makes sense for dynamic sized eigen arrays. It makes no difference for fixed sized types.

Footnote 1: A rare exception can be cases were you would need to do a copy anyway inside the function.

Tim Sylvester
  • 22,897
  • 2
  • 80
  • 94
Homer512
  • 9,144
  • 2
  • 8
  • 25