Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reshaping data from a std::vector<double> into double** of specified dimensions using reinterpret_cast

I have a std::vector<double> containing M*N values, and I'd like to reshape this into a double** which behaves like a double[N][M] multidimensional array. These start and end points may seem strange, but they are unfortunately both decided by external libraries, so there's not much I can do about them. I'm asking this question to see if there's a way to accomplish this without simply copying all the data manually.

I did read this question, with an excellent answer, but it has slightly different starting point - rather than going from std::vector<double> to double**, it goes from double[] to double[][]. I tried to understand what was going on there and apply the same principles to my case, but I can't get my code to work. How do I do this correctly?

const int M = 3;
const int N = 2;
std::vector<double> f = { 0, 1, 2, 3, 4, 5 };

double* f1d = f.data();

// compiles and doesn't crash
// but f2d seems to be unitialized (at least f2d[1][1] is garbage)
// also has the wrong type (double[][] rather than double**)
double (&f2d)[N][M] = reinterpret_cast<double (&)[N][M]>(f1d);

// segfaults when trying to access e.g. f2d[0][0]
double** f2d = reinterpret_cast<double**>(f1d);

Eventually, I need to pass my data into a method which takes a double** parameter, so the following has to compile and run without problem:

#include <iostream>
#include <vector>

void test(int M, int N, double **arr) {
    for (int i = 0; i < N; ++i) {
        for (int j = 0; j < M; ++j) {
            std::cout << arr[i][j] << " ";
        }
        std::cout << std::endl;
    }
};

int main() {
    const int M = 3;
    const int N = 2;
    std::vector<double> f = { 0, 1, 2, 3, 4, 5 };

    // whatever I need to do here to get f2d

    test(M, N, f2d);
    return 0;
}

Expected output:

0 1 2 
3 4 5
like image 342
Tomas Aschan Avatar asked Dec 01 '22 19:12

Tomas Aschan


2 Answers

A double**-based "array" is a completely different beast from an actually multidimensional array, i.e. double[N][M]. It has a completely different layout and stores different information, so you cannot possibly do what you want without storing any additional information.

The way a double**-based "array" works is with a two-level structure, where the first level is an array that contains pointers to various regular one-dimensional arrays. A contiguous multidimensional array cannot function directly as a double**-based "array" because that first level structure with the pointers is nowhere to be seen: the various subarrays in a double[N][M] are implicit from the base address and the sizes.

double[3][2]
+----+----+----+----+----+----+
| 00 | 01 | 02 | 10 | 11 | 12 |
+----+----+----+----+----+----+

double**
+------------+------------+
| 0xDEADD00D | 0xDEADBABE |
+------------+------------+
      |            |
+-----+            |
|                  |
v                  |
+----+----+----+   |
| 00 | 01 | 02 |   |
+----+----+----+   |
                   v
                   +----+----+----+
                   | 10 | 11 | 12 |
                   +----+----+----+

So now, that that is out of the way and we understand how a double**-based "array" works, we can start trying to solve your problem.

What you need to do to get a double** is to provide that first level structure yourself by filling a separate array of pointers with pointers to various addresses within your single contiguous array.

std::vector<double*> index;
index.reserve(M);
for(int i = 0; i < M; ++i) {
    index.push_back(&f[i*N]);
}
double** f2d = index.data();

This gives you minimal hassle, and minimal space overhead. It also performs no copies of the data whatsoever, since we're only collecting a bunch of pointers to the already existing storage.

You end up with a layout that looks like this:

index
  +------------+------------+
  | 0xDEADD00D | 0xDEADBABE |
  +------------+------------+
        |            |
  +-----+        +---+
  |              |
  v              v
  +----+----+----+----+----+----+
f | 00 | 01 | 02 | 10 | 11 | 12 |
  +----+----+----+----+----+----+
like image 82
R. Martinho Fernandes Avatar answered Dec 03 '22 07:12

R. Martinho Fernandes


Obviously, you couldn't get a valid double** out of the array, since there is no array of double* to point to. You'll need to reinterpret the flat array as a two-dimensional array, not an array of pointers.

The first attempt is almost correct; but it reinterprets a reference to the pointer f1d, rather than the array it points to, as a 2-dimensional array. Dereferencing the pointer, and reinterpreting the resulting reference to the array, should work:

double (&f2d)[N][M] = reinterpret_cast<double (&)[N][M]>(*f1d);
                                                         ^

Alternatively, you could reinterpret a reference to f[0] without needing an intermediate pointer.

As you can see, using reinterpret_cast is very error prone. You might consider instead wrapping the vector in a class, with accessor functions to perform the necessary arithmetic for two-dimensional indexing.

UPDATE: If for some reason you really want a double** rather than a two-dimensional array, you'll need to build an array of pointers yourself, along the lines of

std::vector<double*> pointers;
for (i = 0; i < M; ++i) {
    pointers.push_back(&f[i*N]);
}
double** f2d = pointers.data();
like image 25
Mike Seymour Avatar answered Dec 03 '22 08:12

Mike Seymour