Others have explained what void foo(int **p)
does but they didn't answer another part of your question: the "when the parameter is not meant to be a 2D array".
The answer here is the parameter int**p
is NEVER a 2D array. Even if you dereference it as p[row][column] there are a couple major differences:
1) The memory is not organized as a 2D array but rather it's a pointer to a 1D array of "row" pointers that each point to a separate 1D array of integers (indexed here by "column").
2) The "rows" do not have to be contiguous (i.e. you can't add a row width to get the next row).
3) The "rows" do not have to be the same size (they can vary in number of accessible elements)
To pass a 2D array to a function you can use a variety of function signatures. The easiest is void foo(int p[25][15])
(where 25 and 15 are your array row and column size).
The following function signatures for 2D arrays generate the same code when called as well: void foo(int (&p)[25][15])
and void foo(int (*p)[25][15])
and void foo(int p[][15])
. The last version here p[][15]
allows you to handle 2D arrays with a fixed column width but a varying number of rows.
At the risk of being too "clever", you can pass a 2D array as void foo2(int *p)
if you pass the address of the first element (or you can use int a[25][15]; foo2(a[0]);
). However, in foo2, you will have to use pointer math to access the rows and columns of the 2D array and you will also have to change the way you call the function (to pass an int * rather than an array pointer). This "clever" technique is occasionally useful in C when you have to handle 2D arrays of varying dimension sizes. In C++, it is easier to just use a template with the array sizes as template parameters to handle 2D arrays of various sizes.