Search code examples
c++arraysc++11multidimensional-arrayreinterpret-cast

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

Solution

  • 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 |
      +----+----+----+----+----+----+