Alright, I'd like to answer my own question too. There are many good ideas here, but let me post this simple and low-memory-cost solution. For devices of minimal memory (ex: an ATTiny AVR microcontroller with 512 bytes of RAM and a few kB program flash memory), I think this may be the best solution. I've tested it; it works. Use an array of pointers to int8_t's--ie: an array of pointers to 1D arrays, as follows:
int8_t intArray10[][2] = {-2, -1,
0, 1,
2, 3,
4, 5,
6, 7};
int8_t intArray11[][3] = {10, 9, 8,
7, 6, 5,
4, 3, 2,
1, 0, -1};
int8_t intArray12[][2] = {100, 101,
102, 103};
//get some array specs we need about the arrays
byte arrayLengths[] = {sizeof(intArray10)/sizeof(int8_t), sizeof(intArray11)/sizeof(int8_t),
sizeof(intArray12)/sizeof(int8_t)}; //redundant division since int8_t is 1 byte, but I want to leave it for verboseness and extension to other types
byte arrayCols[] = {2, 3, 2};
//make pointers to int8_t's and point each pointer to the first element of each 2D array, as though each 2D array was a 1D array
//-this works because the arrays use contiguous memory
int8_t *intArray10_p = &(intArray10[0][0]);
int8_t *intArray11_p = &(intArray11[0][0]);
int8_t *intArray12_p = &(intArray12[0][0]);
//make an array of those pointers to 1D arrays
int8_t *intArrayOfnDimArray[] = {intArray10_p, intArray11_p, intArray12_p};
//print the 3 arrays via pointers to (contiguous) 1D arrays:
//-Note: this concept and technique should work with ANY contiguous array of ANY number of dimensions
for (byte i=0; i<sizeof(intArrayOfnDimArray)/sizeof(intArrayOfnDimArray[0]); i++) //for each 2D array
{
for (byte j=0; j<arrayLengths[i]; j++) //for all elements of each 2D array
{
Serial.print(intArrayOfnDimArray[i][j]); Serial.print("/"); //now read out the 2D array values (which are contiguous in memory) one at a time; STANDARD ARRAY ACCESS TECHNIQUE
Serial.print(*(intArrayOfnDimArray[i] + j)); Serial.print("/"); //ARRAY/POINTER TECHNIQUE (extra teaching moment)
Serial.print((*(intArrayOfnDimArray + i))[j]); Serial.print("/"); //POINTER/ARRAY TECHNIQUE
Serial.print(*(*(intArrayOfnDimArray + i) + j)); //POINTER/POINTER TECHNIQUE
//add a comma after every element except the last one on each row, to present it in 2D array form
static byte colCount = 0; //initialize as being on "Column 1" (0-indexed)
if (colCount != arrayCols[i]-1) //if not on the last column number
Serial.print(", ");
else //colCount==arrayCols[i]-1 //if we *are* on the last column number
Serial.println();
colCount++;
if (colCount==arrayCols[i])
colCount = 0; //reset
}
Serial.println(F("-----")); //spacer
}
Output:
-2/-2/-2/-2, -1/-1/-1/-1
0/0/0/0, 1/1/1/1
2/2/2/2, 3/3/3/3
4/4/4/4, 5/5/5/5
6/6/6/6, 7/7/7/7
-----
10/10/10/10, 9/9/9/9, 8/8/8/8
7/7/7/7, 6/6/6/6, 5/5/5/5
4/4/4/4, 3/3/3/3, 2/2/2/2
1/1/1/1, 0/0/0/0, -1/-1/-1/-1
-----
100/100/100/100, 101/101/101/101
102/102/102/102, 103/103/103/103
-----
Note: as Thomas Matthews teaches in his answer, to access a specific 2D array, row, and column, use the following:
byte index = row * maximum_columns + column;
which in my above example would be:
byte rowColIndex = desiredRow * arrayCols[desired2DArrayIndex] + desiredColumn;
int8_t val = intArrayOfnDimArray[desired2DArrayIndex][rowColIndex];
Note: byte is equivalent to uint8_t.
{{ x, y }, { z, 43 }}. Well, seriously: Usestd::vector<std::vector<int8_t>>.What must one do to make an array of arrays of different element sizes?in C++ .. you could use an STL containerstd::array<>instead.