Discussion:
Passing result of block() as non-const reference-to-matrix?
Sidney Cadot
2010-07-09 09:28:45 UTC
Permalink
Hello,

Today I found, to my surprise, that passing the result of a Matrix::block() method to a function that takes a reference to an Eigen::Matrix doesn't seem to work. The program (which doesn't compile) demonstrates the problem.

I know that it /is/ possible to pass the result of a Matrix::block() const call to a const reference to an Eigen::Matrix. Is it intentional that the non-const version of this does not work?

Any help would be greatly appreciated.

Regards, Sidney

/////////////////////// code fragment starts here

#include <Eigen/Core>

void incrementBottomRight(Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> & m)
{
m(m.rows() - 1, m.cols() - 1) += 1.0;
}

int main()
{
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> testMatrix(3, 3);

// This works fine
incrementBottomRight(testMatrix);

// This gives a compiler error
incrementBottomRight(testMatrix.block(0, 0, 2, 2));

std::cout << testMatrix << std::endl;

return 0;
}
Hauke Heibel
2010-07-09 09:52:52 UTC
Permalink
Hi Sidney,

that is expected since a matrix block is not a matrix. It is a special
matrix expression which shares operations defined in MatrixBase and
DenseBase with matrices. So you need to declare your function as a
template function like this

template <typename Derived>
void incrementBottomRight(Eigen::MatrixBase<Derived> & m)
{
   m(m.rows() - 1, m.cols() - 1) += 1.0;
}

In case this is not an option you could probably do

void incrementBottomRight(Eigen::Block<MatrixXd> & m)
{
   m(m.rows() - 1, m.cols() - 1) += 1.0;
}

but then the function will be limited to blocks only.

Regards,
Hauke
Post by Sidney Cadot
Hello,
Today I found, to my surprise, that passing the result of a Matrix::block() method to a function that takes a reference to an Eigen::Matrix doesn't seem to work. The program (which doesn't compile) demonstrates the problem.
I know that it /is/ possible to pass the result of a Matrix::block() const call to a const reference to an Eigen::Matrix. Is it intentional that the non-const version of this does not work?
Any help would be greatly appreciated.
Regards, Sidney
/////////////////////// code fragment starts here
#include <Eigen/Core>
void incrementBottomRight(Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> & m)
{
   m(m.rows() - 1, m.cols() - 1) += 1.0;
}
int main()
{
   Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> testMatrix(3, 3);
   // This works fine
   incrementBottomRight(testMatrix);
   // This gives a compiler error
   incrementBottomRight(testMatrix.block(0, 0, 2, 2));
   std::cout << testMatrix << std::endl;
   return 0;
}
Martin Senst
2010-07-09 10:01:39 UTC
Permalink
Hi everyone,

a second problem is that the result of testMatrix.block(0, 0, 2, 2) is a
temporary object which cannot be bound to an lvalue reference. I'm not sure
how to circumvent this problem in C++ 03, but if you are using C++0x, you can
define a second function which takes an rvalue reference. The following
program works with g++ 4.5 and -std=c++0x, and outputs

0 0 0
0 1 0
0 0 1


Cheers,
Martin




#include <Eigen/Core>

template <typename Derived>
void incrementBottomRight(Eigen::MatrixBase<Derived>& m)
{
m(m.rows() - 1, m.cols() - 1) += 1.0;
}


template <typename Derived>
void incrementBottomRight(Eigen::MatrixBase<Derived>&& m)
{
m(m.rows() - 1, m.cols() - 1) += 1.0;
}


int main()
{
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> testMatrix(3, 3);

testMatrix.setZero();

// This uses the first version with an lvalue-reference parameter
incrementBottomRight(testMatrix);

// This uses the second version with an rvalue-reference parameter
incrementBottomRight(testMatrix.block(0, 0, 2, 2));

std::cout << testMatrix << std::endl;

return 0;
}
Post by Hauke Heibel
Hi Sidney,
that is expected since a matrix block is not a matrix. It is a special
matrix expression which shares operations defined in MatrixBase and
DenseBase with matrices. So you need to declare your function as a
template function like this
template <typename Derived>
void incrementBottomRight(Eigen::MatrixBase<Derived> & m)
{
m(m.rows() - 1, m.cols() - 1) += 1.0;
}
In case this is not an option you could probably do
void incrementBottomRight(Eigen::Block<MatrixXd> & m)
{
m(m.rows() - 1, m.cols() - 1) += 1.0;
}
but then the function will be limited to blocks only.
Regards,
Hauke
Post by Sidney Cadot
Hello,
Today I found, to my surprise, that passing the result of a
Matrix::block() method to a function that takes a reference to an
Eigen::Matrix doesn't seem to work. The program (which doesn't compile)
demonstrates the problem.
I know that it /is/ possible to pass the result of a Matrix::block()
const call to a const reference to an Eigen::Matrix. Is it intentional
that the non-const version of this does not work?
Any help would be greatly appreciated.
Regards, Sidney
/////////////////////// code fragment starts here
#include <Eigen/Core>
void incrementBottomRight(Eigen::Matrix<double, Eigen::Dynamic,
Eigen::Dynamic> & m) {
m(m.rows() - 1, m.cols() - 1) += 1.0;
}
int main()
{
Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> testMatrix(3, 3);
// This works fine
incrementBottomRight(testMatrix);
// This gives a compiler error
incrementBottomRight(testMatrix.block(0, 0, 2, 2));
std::cout << testMatrix << std::endl;
return 0;
}
Hauke Heibel
2010-07-09 10:06:26 UTC
Permalink
Yes, this is an ugly issue with expression templates. As you said, the
cleanest way is to use rvalue references.

A second option is to define the function as

template <typename Derived>
void incrementBottomRight(const Eigen::MatrixBase<Derived>& m)

and const_cast to "Eigen::MatrixBase<Derived>&" -- as I said, ugly.
The third an most portable option is in fact to call the function like
this:

Eigen::Block<MatrixXd> b = m.testMatrix.block(0, 0, 2, 2); // this has
quasi zero cost
incrementBottomRight(b);

But in case you have access to them rvalue references are the best option here.

Cheers,
Hauke
Post by Martin Senst
Hi everyone,
a second problem is that the result of testMatrix.block(0, 0, 2, 2) is a
temporary object which cannot be bound to an lvalue reference. I'm not sure
how to circumvent this problem in C++ 03, but if you are using C++0x, you can
define a second function which takes an rvalue reference. The following
program works with g++ 4.5 and -std=c++0x, and outputs
0 0 0
0 1 0
0 0 1
Cheers,
Martin
#include <Eigen/Core>
template <typename Derived>
void incrementBottomRight(Eigen::MatrixBase<Derived>& m)
{
   m(m.rows() - 1, m.cols() - 1) += 1.0;
}
template <typename Derived>
void incrementBottomRight(Eigen::MatrixBase<Derived>&& m)
{
   m(m.rows() - 1, m.cols() - 1) += 1.0;
}
int main()
{
   Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> testMatrix(3, 3);
   testMatrix.setZero();
   // This uses the first version with an lvalue-reference parameter
   incrementBottomRight(testMatrix);
   // This uses the second version with an rvalue-reference parameter
   incrementBottomRight(testMatrix.block(0, 0, 2, 2));
   std::cout << testMatrix << std::endl;
   return 0;
}
Post by Hauke Heibel
Hi Sidney,
that is expected since a matrix block is not a matrix. It is a special
matrix expression which shares operations defined in MatrixBase and
DenseBase with matrices. So you need to declare your function as a
template function like this
template <typename Derived>
void incrementBottomRight(Eigen::MatrixBase<Derived> & m)
{
   m(m.rows() - 1, m.cols() - 1) += 1.0;
}
In case this is not an option you could probably do
void incrementBottomRight(Eigen::Block<MatrixXd> & m)
{
   m(m.rows() - 1, m.cols() - 1) += 1.0;
}
but then the function will be limited to blocks only.
Regards,
Hauke
Post by Sidney Cadot
Hello,
Today I found, to my surprise, that passing the result of a
Matrix::block() method to a function that takes a reference to an
Eigen::Matrix doesn't seem to work. The program (which doesn't compile)
demonstrates the problem.
I know that it /is/ possible to pass the result of a Matrix::block()
const call to a const reference to an Eigen::Matrix. Is it intentional
that the non-const version of this does not work?
Any help would be greatly appreciated.
Regards, Sidney
/////////////////////// code fragment starts here
#include <Eigen/Core>
void incrementBottomRight(Eigen::Matrix<double, Eigen::Dynamic,
Eigen::Dynamic> & m) {
   m(m.rows() - 1, m.cols() - 1) += 1.0;
}
int main()
{
   Eigen::Matrix<double, Eigen::Dynamic, Eigen::Dynamic> testMatrix(3, 3);
   // This works fine
   incrementBottomRight(testMatrix);
   // This gives a compiler error
   incrementBottomRight(testMatrix.block(0, 0, 2, 2));
   std::cout << testMatrix << std::endl;
   return 0;
}
Sidney Cadot
2010-07-09 13:46:51 UTC
Permalink
Hauke, Martin: thanks for your suggestions.

It's a pity that there is no clean solution that is portable to pre-C++0x. I still don't quite get that it does work as expected when I pass a const Block to a const Matrix & -- it is not clear for me why that /does/ work.

I have a feeling that I (as an API user) need to know too much about the intricacies of the Eigen data-structures in this case, while I can usually just really on stuff to (almost magically) work; A Matrix::block() result /usually/ returns something that behaves as Matrix, but not always.

Best regards, Sidney
Martin Senst
2010-07-09 14:23:53 UTC
Permalink
Post by Sidney Cadot
Hauke, Martin: thanks for your suggestions.
It's a pity that there is no clean solution that is portable to pre-C++0x.
I still don't quite get that it does work as expected when I pass a const
Block to a const Matrix & -- it is not clear for me why that /does/ work.
That's because const lvalue references are allowed to bind to temporary
objects, while non-const lvalue references are not.
The reason is that a non-const reference parameter means that your function
intends to modify the referenced object - otherwise you would use a const
reference. However, modifying a temporary object usually doesn't make much
sense and is usually a bug - therefore the C++ standard prevents you from
doing so.

Furthermore, if you have a function
void f(const MatrixXd& m);
and call it with a MatrixXd::block(), then the block expression is converted
to a plain matrix. That's okay since f only reads m and doesn't modify it.
If m was non-const, then converting the block object to a plain matrix would
result in a bug, since the modification of the temporary object m inside f
would not be visible outside of f, which is not what you indended.

By the way, even if your function does not modify its parameter, the function
declaration
template <typename Derived>
void f(const MatrixBase<Derived>& m)
is more efficient, since it avoids the evaluation of the block expression to a
plain matrix.

Cheers
Martin
Post by Sidney Cadot
I have a feeling that I (as an API user) need to know too much about the
intricacies of the Eigen data-structures in this case, while I can usually
just really on stuff to (almost magically) work; A Matrix::block() result
/usually/ returns something that behaves as Matrix, but not always.
Best regards, Sidney
Benoit Jacob
2010-07-09 17:03:37 UTC
Permalink
Post by Sidney Cadot
Hauke, Martin: thanks for your suggestions.
It's a pity that there is no clean solution that is portable to pre-C++0x. I still don't quite get that it does work as expected when I pass a const Block to a const Matrix & -- it is not clear for me why that /does/ work.
That does /not/ work :-) Try it, try to pass a Block as a const
Matrix&, it won't compil, since these are different types. On the
other hand, passing a Block as a template<typename T> const
MatrixBase<T>& will work, because (in the matrix case) Block inherits
MatrixBase<Block>, so the compiler will be able to resolve T=Block.
Post by Sidney Cadot
I have a feeling that I (as an API user) need to know too much about the intricacies of the Eigen data-structures in this case, while I can usually just really on stuff to (almost magically) work; A Matrix::block() result /usually/ returns something that behaves as Matrix, but not always.
Expression templates come at the cost of some complexity, need the
user to understand some concepts. It's true. But it's worth it, so
it's a documentation issue.

Documentation ninjas: this is the reason why I believe we really need
to end the tutorial on one page explaining the vital minimum stuff to
know about base classes, passing Eigen objects to functions, etc.

Benoit
Post by Sidney Cadot
Best regards, Sidney
Gael Guennebaud
2010-07-09 17:27:19 UTC
Permalink
Post by Benoit Jacob
Post by Sidney Cadot
It's a pity that there is no clean solution that is portable to pre-C++0x. I still don't quite get that it does work as expected when I pass a const Block to a const Matrix & -- it is not clear for me why that /does/ work.
That does /not/ work :-)
that does work ;) The block expression will be evaluated into a
temporary Matrix object. (unless the Matrix<> type is templated as
well but this is not the case in his example)

gael
Benoit Jacob
2010-07-09 17:32:03 UTC
Permalink
Post by Gael Guennebaud
Post by Benoit Jacob
Post by Sidney Cadot
It's a pity that there is no clean solution that is portable to pre-C++0x. I still don't quite get that it does work as expected when I pass a const Block to a const Matrix & -- it is not clear for me why that /does/ work.
That does /not/ work :-)
that does work ;) The block expression will be evaluated into a
temporary Matrix object. (unless the Matrix<> type is templated as
well but this is not the case in his example)
oooh ok :)
Post by Gael Guennebaud
gael
Loading...