I have a dynamic Eigen::MatrixXi
, from which I'd like to create different "views" using Eigen::Ref
in combination with Eigen::Block
. In other words, I'd like to refer to different parts of an Eigen::MatrixXi
using the same variable, without copying elements around. For example:
#include <iostream>
#include <Eigen/Dense>
int main() {
Eigen::MatrixXi m(4, 4);
m << 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16;
std::cout << m << "\n\n";
Eigen::Ref<Eigen::MatrixXi> ref_m = Eigen::Block<Eigen::MatrixXi>(m, 0, 0, 2, 2);
std::cout << ref_m.rows() << "x" << ref_m.cols() << "\n";
std::cout << ref_m << "\n\n";
ref_m = Eigen::Block<Eigen::MatrixXi>(m, 0, 0, 3, 3);
std::cout << ref_m.rows() << "x" << ref_m.cols() << "\n";
std::cout << ref_m << "\n\n";
}
The problem here is that once I assign to ref_m
in Eigen::Ref<Eigen::MatrixXi> ref_m = Eigen::Block<Eigen::MatrixXi>(m, 0, 0, 2, 2);
, I get a view of the first 2 rows and 2 columns; however, if I try to change the view and re-assign to include now the first 3 rows and 3 columns, in ref_m = Eigen::Block<Eigen::MatrixXi>(m, 0, 0, 3, 3);
, it looks like the ref_m
doesn't refer to the new block, but points to the initial one.
My question is how can I "re-assign" to an Eigen::Ref
? If I cannot, then why is the assignment operator allowed? Are there any ways of achieving this without doing extra copies (one can simply change the type from Eigen::Ref<Eigen::MatrixXi>
to Eigen::MatrixXi
, but this will copy the block instead of simply referring to it.)
CodePudding user response:
You can use placement-new. Eigen's documentation only mentions this for Map but it can be used for expressions and Refs, too.
Eigen::Ref<Eigen::MatrixXi> ref_m { m.topLeftCorner(2,2) };
std::cout << ref_m << "\n\n";
new (&ref_m) Eigen::Ref<Eigen::MatrixXi> { m.topLeftCorner(3,3) };
std::cout << ref_m << "\n\n";
Warning
This works fine for mutable Refs, but be careful with const Refs (Eigen::Ref<const Eigen::MatrixXi>
). The reason is that these do take copies if the inner stride is above 1 (typically referencing a single row). In that case you have to ensure that the destructor runs or you leak memory.
Eigen::Ref<const Eigen::RowVectorXi> ref_v { m.row(3) };
std::cout << ref_v << '\n';
new (&ref_v) Eigen::Ref<const Eigen::RowVectorXi>{m.row(2)}; // memory leak!
Possible workarounds:
- Invoking the destructors explicitly
- Using Block expressions or Maps, not Refs (because those have empty destructors). For example if the block type never changes, you can use this zero-overhead version:
auto r_v = m.row(3);
std::cout << r_v << '\n';
new (&r_v) (decltype(r_v)){m.row(2)};
std::cout << r_v << '\n';
- Use
std::optional
to handle the lifecycle
std::optional<Eigen::Ref<const Eigen::RowVectorXi> > r_v {std::in_place, m.row(3)};
std::cout << *r_v << '\n';
r_v.emplace(m.row(2)); // calls destructor, then placement-new
std::cout << *r_v << '\n';
CodePudding user response:
The Eigen::Ref
class is not designed to be used like that. Normally it's used when you want to pass an Eigen
expression to a function without implementing a template function, like the following example.
void test1(Eigen::MatrixXi &m) { m(1, 1) = 5; }
void test2(Eigen::Ref<Eigen::MatrixXi> m) { m(1, 1) = 5;}
...
Eigen::MatrixXi m(4, 4);
test1(m.block(0, 0, 2, 2)); // This will not work.
test2(m.block(0, 0, 2, 2)); // But this will work.
If you want to reuse a variable for an Eigen::Block
, use a pointer like this, although it's not an Eigen
style.
typedef Eigen::Block<Eigen::MatrixXi> B;
std::unique_ptr<B> b(new B(m, 0, 0, 2, 2));
...
b.reset(new B(m, 1, 1, 2, 2));