5

I ve got a function that takes 3 parameteres, first one is **double.

 normalizeDataZeroMeanUnitSD(double ** trainingActions, int numberOfTrainingActions, int descriptorDimension)

When I call it from main, I am trying to use normalizeDataZeroMeanUnitSD(data, 681, 24); however, I am receiving

cannot convert parameter 1 from 'double [681][24]' to 'double **'

This is how I construct the data array:

fstream infile;
infile.open("gabor\\Data.txt");

double data[681][24];

while (!infile.eof()) 
     {
        for(int j=0;j<681;j++) 
        {
            for(int k=0; k<24;k++) 
            {

                infile >> data[j][k];

           }

        }

 } 
 infile.close();

Is there a way to do the same using **data?

snake plissken
  • 2,649
  • 10
  • 43
  • 64

8 Answers8

6

The error is pretty clear: Datatype double [681][24] is not the same as double **. While it's true that double[681] can decay to a pointer to its first element (thus, double*), that does not imply that double[681][24] can decay to double**.

Think about it this way: double** implies a pointer to many pointers. But double[][] does not have ANY pointers in it. At best, an array of ANY dimensions still only has, at very most, one pointer: to the beginning of its contiguous storage.

tenfour
  • 36,141
  • 15
  • 83
  • 142
4

You could use a template:

template<std::size_t M, std::size_t N>
void normalizeDataZeroMeanUnitSD(double (&trainingActions)[M][N], int descriptorDimension)
{
   for( std::size_t m = 0; m < M; ++m )
   {
      for( std::size_t n = 0; n < N; ++n )
      {
          trainingActions[m][n] = ...;
      }
   }
}

But beware of code bloat if you call this with many differently sized arrays.

metal
  • 6,202
  • 1
  • 34
  • 49
  • 1
    +1; this is probably the best solution. And I wouldn't worry about bloat: If you need it, you need it. IMO, This is much cleaner than passing a `double*` and two dimension size args. – tenfour Feb 12 '14 at 15:14
  • "Code bloat" isn't quite the applicable term here since the compiler adds the needed code for you. You are right that this could result in an oversized executable. However, the real problem with this is that you need to know the size at compile time, which may not be feasible. – Nicu Stiurca Feb 12 '14 at 15:14
  • 1
    Pass array by reference, so `M` can be deduced. – Jarod42 Feb 12 '14 at 15:14
  • This is fine if you don't mind restricting the dimensions to compile-time constants. You can drop the other function parameters, since they're now redundant. – Mike Seymour Feb 12 '14 at 15:20
  • 1
    @tenfour: No, it's not being passed by reference. It's decaying to a pointer, so that `M` can't be deduced. `double (&trainingActions)[M][N]` would be a reference. (At least, it was before I corrected the answer). – Mike Seymour Feb 12 '14 at 15:20
3

A 2d array is a continuous area of storage. The function expects a pointer to pointers. These are incompatible.

Sebastian Redl
  • 69,373
  • 8
  • 123
  • 157
3

Use any of the following declarations. Both are equivalent.

NormalizeDataZeroMeanUnitSD(double trainingActions[][24], int numberOfTrainingActions, int descriptorDimension)

NormalizeDataZeroMeanUnitSD(double trainingActions[681][24], int numberOfTrainingActions, int descriptorDimension)

When you declare a 2D array it takes up contiguous memory locations. So you need to specify at least the number of columns (in case of row major architecture).

For row major and column major definitions, have a look at this.

For your edited question, yes you can declare using **data. Dynamically allocate the data array. But remember to free it when you're done with it.

double **data=new double*[681];
for (int i=0;i<681;i++)
{
  data[i]=new double[24];
}

//do what you want to do


for (int i=0;i<681;i++)
{
  delete [] data[i];
}

delete [] data;

Now your function prototype can be like void func(double **pp) because data is a pointer not a 2D array.

HelloWorld123456789
  • 5,299
  • 3
  • 23
  • 33
3

The function expects an array of pointers to arrays; you have an array of arrays. Some options are:

  • change the function to take a more friendly type, perhaps double* pointing to the first element of a contiguous 2-dimensional array; or
  • build a separate array of pointers pointing to each row of your 2-dimensional array; or
  • restructure your data into an array of pointers to arrays.
Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
3

Here is a constructive answer for how to make it work.

Basically, you need to generate an array that has pointers to each 1D slice of your 2D array.

double data[N][M] = {...};
double *dataPtrs[N];

for(size_t n=0; n<N; ++n) {
  dataPtrs[n] = data[n];
}

normalizeDataZeroMeanUnitSD(dataPtrs, N, M); // order of N and M might be wrong
Nicu Stiurca
  • 8,747
  • 8
  • 40
  • 48
3

Yay, I get to rant about this again.

In C++, despite having similar syntax, 2D arrays are NOT jagged arrays. 2D arrays (double foo[681][24]) are allocated contiguously in memory. When you deference a 2D array (foo[j][i]) it actually does *(foo+24*i+j). This is all done under the hood. The sizeof(foo)==sizeof(double)*681*24.

Jagged arrays are (double** bar;). This is a bunch of different arrays: first, you allocate an array of pointer, 268 members long. Each pointer will point to an array of doubles, 24 elements long. Bar is just a pointer, so sizeof(bar)==sizeof(void*).

More annoyingly, 2D arrays (or a static array of any dimension) behave the opposite of all other types in C++ in the following reguard: they are passed implicitly by reference, causing the weird phenomenon below.

void foo(double bar[24][24]) { std::cout << sizeof(bar) << std::endl;}
int main() {
  double test[24][24];
  std::cout << sizeof(test) << std::endl;//returns sizeof(double)*24*24
  foo(test);//returns sizeof(void*), implicitly passed by reference, opossite of the rest of c++!
IdeaHat
  • 7,641
  • 1
  • 22
  • 53
  • Arrays of any dimension are passed as a pointer to the first element (which in the case of a 2-D array will itself have pointer-to-array type). This is indeed annoying, although it's not specific to 2-D arrays. – Steve Jessop Feb 12 '14 at 15:15
  • Pass test by reference instead of pointer to get the expected result. – Jarod42 Feb 12 '14 at 15:21
  • `void foo(double bar[24][24])` is equivalent to `void foo(double *bar[24])`. Use `void foo(double (&bar)[24][24])` to get expected result. – Jarod42 Feb 12 '14 at 15:26
  • 1
    @Jarod42 while this is a true statement, the fact that if you do `typedef int foo4[4];` that sizeof(objects of type foo4) is not consistent is...annoying. – IdeaHat Feb 12 '14 at 15:28
2

double[][] is not the same thing as double**.

double** is a pointer to pointers. double[][] is a 2-dimensional array allocated as continuous storage.

In order to pass a "2-dimensional array" to the function, you need to create an array of pointers to arrays. For example:

double* array_2d[681];
for(unsigned int i=0; i<681; ++i) {
    array_2d[i] = new double[24];
}
normalizeDataZeroMeanUnitSD(array_2d, 681, 24);

Remember to later delete[] each element of array_2d!

Better yet, change normalizeDataZeroMeanUnitSD to take a reference to std::vector<std::vector<double>>, and you no longer have to worry about memory management, nor passing the correct dimensions to the function.

zennehoy
  • 6,405
  • 28
  • 55