views:

363

answers:

4
+1  Q: 

2D arrays with C++

I have a function that takes a pointer to a pointer an as argument.

func(double **arr, int i);

where in the main function the array is defined as follows:

double arr[][] = //some initialization here;

How can I call this function from my main code. I tried the following but it gives an error

func (&arr);

Doesn't work. Any help would be much appreciated. Thanks

A: 

If I am not mistaken, **arr is simply a useless cast, meaning its interpreted as "the value of the pointer" wich is simply the variable. In this case, you should simply declare the function:

func(double *arr, int i);

and pass it you array, since a 2D array is a single array of arrays. Anyone feel free to correct me if I am mistaken or confirm. Thx

David Menard
I cannot change the declaration of the func. I thought of this myself before but I am not allowed to change it because it is being deployed currently.
VP
You're mistaken, because `**` is not a cast at all.
Pavel Minaev
+5  A: 

A double **p is not the same thing as a double[][] a, so you can't pass one as the other.

In particular the two-dimensional array variable is a (single!) block of memory containing doubles which you can access with the [][] syntax. This requires that the compiler know the dimension of the array, so that it can compute the correct offset to each element. It can also decay transparently to an pointer to that block of memory, but doing so loses the understanding of how to access that memory as a two dimensional array: it becomes effectively a pointer to double.

+----+           +---------+---------+---------+
| (a---------->) | a[0][0] | a[0][1] | a[0][2] | ...
+----+           +---------+---------+---------+ 
                 | a[1][0] | a[1][2] | ...
                 +---------+---------+
                   ...

The thing expected by the function is a pointer to a block of memory which contains one or more pointers to additional blocks of memory containing doubles.

+---+       +------+      +---------+---------+
| p-------->| p[0]------->| p[0][0] | p[0][3] | ...
+---+       +------+      +---------+---------+
            | p[1]--\    
            +------+ \    +---------+---------+
             ...      --->| p[1][0] | p[1][4] | ...
                          +---------+---------+

While the syntax looks similar, these two structures have totally different semantics.


For a more complete discussion, see my answer to a previous questions (which actually address c, but the issues are the same).

dmckee
so then how do i pass the array to my func? I am confused. I want to pass it to func without changing its declaration.
VP
+15  A: 

The type of arr is double[X][Y] - that is, an array of X arrays of Y doubles - where X and Y depend on your initializers. This is not the same as pointer type. However, according to C conversion rules, an array can decay into a pointer to its element. In your case, the type resulting from such decay will be double(*)[Y] - a pointer to an array of Y doubles. Note that this is a pointer to array, not an array of pointers, so it will not decay any further. At this point, you get a type mismatch, since your function expects double**.

The correct way to handle this is to treat the array as single-dimensional, and pass width along. So:

void func(double* arr, int w) {
   // arr[2][3]
   arr[2*w + 3] = ...;
}


double x[6][8] = { ... };
func(&x[0][0], 8);

In C++ in particular, if you always have statically allocated arrays of well-known (but different) types, you may be able to use templates and references like this:

template <int W, int H>
inline void func(const double (&arr)[W][H]) {
  arr[2][3] = ...;
}

double x[6][8] = { ... };
func(x); // W and H are deduced automatically

However, this won't work when all you have is a pointer (e.g. when the array is new-allocated, and its size is calculated at runtime). For the most general case, you should use C++ containers instead. With standard library only, one typically uses vector of vectors:

#include <vector>

void func(std::vector<std::vector<double> > arr) {
  arr[2][3] = ...;
}

std::vector<std::vector<double> > x(6, std::vector<double>(8));
x[0][0] = ...;
...
func(x);

If you can use Boost, it has a very nice MultiArray library in it:

void func(boost::multi_array<double, 2> arr) { // 2 means "2-dimensional"
  arr[2][3] = ...;
}

boost::multi_array<double, 2> x(boost::extents[6][8]);
x[0][0] = ...;
...
func(x);

[EDIT] you say that you cannot change the definition of your function. If so, then it really is a function treating its argument as an array of pointers, so you should just allocate your data structure accordingly. For example:

double x1[8] = { 1, 2, ... };
double x2[8] = { 3, 4, ... };
...
double* x[6] = { x1, x2, ... };

func(x);
Pavel Minaev
Nice and comprehensive. But so what if you can't change the fn? Just wrap it so the top level array of arrays is stack-local (either stack alloc, or exception-safe heap). That is, the 2d array itself contains contiguous 1d arrays, and you can just point to them.
wrang-wrang
Alternatively, you can use the nice initializer syntax for a bunch of separate 1d arrays and point to those. Array initializer syntax is only available at top-level scope, right?
wrang-wrang
Good point, I'll edit. Array initializer is available in any scope (including local/auto), but not in `new`.
Pavel Minaev
A: 
func(double *arr[100])

You need to tell the compiler the size of the dimension so it can create the correct address

  arr[y][x]

turns into

  arr[100*x+y]

If you define your function as func(double **a) then you are saying a pointer to an array of pointers as dmckee pointed out

shimpossible