4

What happens when you pass a matrix object into a function as a MatrixBase reference? I do not get what really happens behind the scenes.

An example function code would be:

#include <Eigen/Core>
#include <iostream>

using namspace Eigen;

template <typename Derived>
void print_size(const MatrixBase<Derived>& b)
{
  std::cout << "size (rows, cols): " << b.size() << " (" << b.rows()
            << ", " << b.cols() << ")" << std::endl;
  std::cout << sizeof(b) << std::endl;
}

int main() {
    Matrix<float, 2, 2> m;
    m << 0.0, 0.1,
         0.2, 0.3;

    print_size(m);
    std::cout << sizeof(m) << std::endl;
}

It gives the following output:

size (rows, cols): 4 (2, 2)
1
16

Where does the 16 vs. 1 difference come from?

And also why would a conversion be necessary?

Thanks in advance!

OnurA
  • 611
  • 2
  • 11
  • 25
  • 1
    Oh wait, brain fart! I don't understand how `sizeof` of a reference could ever be 1. This question is more interesting than I initially thought. I thought initially there was some kind of implicit type conversion going on, but `sizeof` for a reference should still be analogical to doing sizeof on a pointer. –  Jan 24 '18 at 14:08
  • 1
    @TeamUpvote - sizeof for reference is just sizeof for referenced type. Add to `main()` `std::cout << sizeof(MatrixBase) << std::endl;` and you'll see why all of this is as it is – PiotrNycz Jan 24 '18 at 14:11

2 Answers2

7

sizeof is evaluated at compile time, so it is concerned with the declared (static) type of objects. b is of type MatrixBase<Derived> (ignoring the reference, just like sizeof does), which is most likely an empty base class, and hence has size 1.

m, on the other hand, is of type Matrix<float, 2, 2>, which apparently has size 16 on your platform.

I've created a live example demonstrating this behaviour of sizeof.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • The part about ignoring the reference I didn't quite get. I thought `sizeof(reference_type)` would be analogical to `sizeof(pointer)`, like 4 bytes on 32-bit or 8 bytes on 64-bit. It felt like there was slicing going on but I'm still confused why `sizeof` for a reference type could ever be 1 byte. –  Jan 24 '18 at 14:13
  • 3
    @TeamUpvote sizeof applied to a reference returns size of the referenced type. That is just how it is defined. – Jaa-c Jan 24 '18 at 14:14
  • 1
    @TeamUpvote When `sizeof` is applied to a reference or reference type, it gives the size of the referenced type. – Angew is no longer proud of SO Jan 24 '18 at 14:15
  • Wow! I always thought it was synonymous with doing `sizeof` on a pointer. Thanks for teaching me something new! –  Jan 24 '18 at 14:17
  • But, sizeof(MatrixBase >) also gives 1. Even though the reference is removed. – OnurA Jan 24 '18 at 14:20
  • 2
    @OnurA Exactly. As the answer says, `sizeof` ignores references (that is, it gives the exact same value for `T` and for `T&`, for any `T`). – Angew is no longer proud of SO Jan 24 '18 at 14:20
  • @Angew Actually I had a super brain fart there because that makes total sense -- just as `sizeof(*ptr)` would give sizeof a pointee type, `sizeof(ref)` makes sense to give size of referenced type! I've been working in C at my workplace lately and have forgotten some basics of C++. –  Jan 24 '18 at 14:29
  • @OnurA I had to look up the `Eigen::Matrix` docs real quick but it looks like it has 6 different template parameters, not 3. The other 3 are apparently optional but you'd probably need to pass them in to avoid the implicit conversion. –  Jan 24 '18 at 14:31
  • @TeamUpvote what I actually wanted to find out was, what happens when the argument is passed. Why would the conversion necessary in that case? – OnurA Jan 24 '18 at 14:47
  • @OnurA Mismatching types combined with some kind of conversion operator or implicit constructor. I would have thought you could omit the optional template parameters though. I'm not 100% sure why it implicitly converts in this case if you at least specify the 3 template parameters, but only explanation with different `sizeof` values given these answers. –  Jan 24 '18 at 14:49
  • 3
    @OnurA The only implicit conversion going on is the derived-to-base *reference* conversion on binding. In reality, it's just the case of static vs. dynamic type. See the live example I've added to the answer. – Angew is no longer proud of SO Jan 24 '18 at 15:19
  • I need to get some new glasses because the whole time I failed to notice that `MatrixBase` was used in the function while `Matrix` was used by the caller. Thought `Matrix` was used for both. I'm reaching that age where I'm neither near-sighted nor far-sighted. I just can't see anything unless it's at a perfect distance. If it's too far I can't see it, if it's too close I can't see it. Doh! –  Jan 24 '18 at 15:28
  • I can confirm your assumption that `MatrixBase` is an empty base class. Eigen uses the CRTP here. If you want the actual size, you can write either `sizeof(Derived)` or `sizeof b.derived()` – chtz Jan 24 '18 at 17:08
3

sizeof works on compile time types. See sizeof

When applied to an expression, sizeof does not evaluate the expression, and even if the expression designates a polymorphic object, the result is the size of the static type of the expression.

This means that you get the size of MatrixBase<Derived> regardless of what type the instance is.

The expression sizeof(b) is exactly the same as if you wrote sizeof(MatrixBase<Derived>).

Jaa-c
  • 5,017
  • 4
  • 34
  • 64