Home > Software design >  Re-assigning to Eigen::Ref
Re-assigning to Eigen::Ref

Time:10-05

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:

  1. Invoking the destructors explicitly
  2. 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';
  1. 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));
  • Related