0

I'm writing a Conway's game of Life simulator for the Arduino. The Arduino environment is kind-of C++, but with static memory allocation only (no new, no malloc() and no STL.)

class Life {

private:

    uint8_t hue;
    uint8_t currState[WIDTH][HEIGHT];
    uint8_t nextState[WIDTH][HEIGHT];

public:

    void draw() {

        // Update the nextState array with the next generation
        for (int x = 0; x < WIDTH; x++) {
            for (int y = 0; y < HEIGHT; y++) {
                nextState[x][y] = alive(x, y) ? hue++ : 0;
            }
        }

        // Copy nextState into leds array
        for (int x = 0; x < WIDTH; x++) {
            for (int y = 0; y < HEIGHT; y++) {
                pixel(x, y) = CHSV(nextState[x][y], 255, 255);
            }
        }

        // make nextState our new currentState, ready for the next generation
        uint8_t tempState[][] = currState;
        currState = nextState;
        nextState = tempState;
    };

//...

};

I'm getting a compilation error on both the declaration of tempState and the last line:

Array has incomplete element type 'uint8_t []'
Array type 'uint8_t [36][20]' is not assignable

Now, I know that in C/C++, "an array is just a pointer to the first element of the array", but the compiler does not seem to agree. What is the magic declaration that will allow me to alias currState to nextState for the next iteration of my draw() method?

Robert Atkins
  • 23,528
  • 15
  • 68
  • 97
  • 2
    *Now, I know that in C/C++, "an array is just a pointer to the first element of the array"* - [Wrong](http://stackoverflow.com/questions/4810664/how-do-i-use-arrays-in-c) – chris Aug 07 '14 at 14:05
  • 1
    There is no "C/C++", there is C and there is C++. This does not look like C at all. – Baum mit Augen Aug 07 '14 at 14:05
  • This is a case where it is not C since C does not have `class` or `public` as reserved words. – Thomas Matthews Aug 07 '14 at 14:10
  • tell compiler at least one of the dimensions' size. – Tuğrul Aug 07 '14 at 14:11
  • In the first sentence of my post, I note *The Arduino environment is kind-of C++*; my problem is "C-style arrays in C++", as opposed to `std::vector`, which I am trying to avoid people telling me to use, as I can't. – Robert Atkins Aug 07 '14 at 14:13
  • I think you could have the private arrays 1 and 2 and boolean variable saying which one you are currently using depending if it is true or false you could call a method getNextState, or getCurrentState element which would based on the booleen return the array (similarly you can manipulate the elements) I know this is not solving the original question but could get you to progress with the code. – cerkiewny Aug 07 '14 at 14:33
  • Unless I'm mistaken you can use templates with Arduino. If you need those perhaps look into using AVR-GCC directly. – shuttle87 Aug 07 '14 at 14:44
  • You're right, I misspoke, templates yes; STL no. – Robert Atkins Aug 07 '14 at 14:54

7 Answers7

3

As described in the comments post you can not simply swap arrays in such fasion the article describing what arrays in C++ actually are How do I use arrays in C++?. In order to swap the pointers like you want you should create the two private arrays and the pointers to them.

So for instance:

private: 
    uint8_t array1[WIDTH][HEIGHT];
    uint8_t array2[WIDTH][HEIGHT];
    uint8_t (*nextState)[WIDTH][HEIGHT];
    uint8_t (*prevState)[WIDTH][HEIGHT];

now if you base every operation on the pointers to nextState and prevState you can swap them as you described. Note that it will not work with 2d arrays as they are not the pointers but the types (as described in the reference article).

Community
  • 1
  • 1
cerkiewny
  • 2,761
  • 18
  • 36
  • Thank you both, this is what I was looking for. Last piece of the puzzle, how do I dereference my `nextState` so I can write to an element in the array, on the line `nextState[x][y] = alive(x, y) ? hue++ : 0;`? – Robert Atkins Aug 07 '14 at 14:56
  • 1
    Ah wait, got it, simply `*nextState[x][y] = ...` – Robert Atkins Aug 07 '14 at 14:58
  • 2
    @RobertAtkins It's actually `(*nextState)[x][y] = ...` Note your code compiles, but does silently the wrong thing. Yet another reason to wrap C-arrays as soon as possible (normally done by `std::vector`/`std::array`, but you can write equivalents yourself) – milleniumbug Aug 07 '14 at 15:06
  • Thanks for that @milleniumbug, that's the kind of help I was looking for originally. C++ is a nightmare. – Robert Atkins Aug 07 '14 at 15:11
1

While it's possible to create and swap two pointers as @cerkiewny has pointed out, I think it's probably simpler and cleaner to put the "update generation" code into a function, and just put in two calls to that function:

update_generation(state1, state2);
update_generation(state2, state1);

You'd write your update_generation something like:

void update_generation(uint8_t gen1[HEIGHT][WIDTH], uint8_t gen2[HEIGHT][WIDTH])

This function would always use gen1 to create gen2. Although the parameter type is silently adjusted to a pointer, you don't have to take that into account--you can still write the code as if you were working directly with a pair of arrays.

In case you care to refer to it, I posted a complete Win32 implementation of John Conway's Game of Life using this approach in a previous answer.

Community
  • 1
  • 1
Jerry Coffin
  • 476,176
  • 80
  • 629
  • 1,111
  • This is a reasonable approach, but as my draw() method is getting called once per frame, I'd have to track odd and even frames. Do-able but not as natural to me as the pointer-swapping. – Robert Atkins Aug 07 '14 at 16:22
1

There are couple of ways to declare pointers to 2D arrays in C++. I'll start with an example using a 1D array.

Say you have an array:

int arr[SIZE];

The following are two valid pointers to the array.

int* ip1 = arr;
int (*ip2)[SIZE] = &arr;

Assuming n is a valid index, they can be used with the syntax:

ip1[n] = 10;
*(ip1+n) = 10;
(*ip2)[n] = 10;

Of the three, the first two are exactly how you would be able use arr. You can use:

arr[n] = 10;
*(arr+n) = 10;

You cannot use:

(*arr)[n] = 10;

In that sense, ip1 is a true alias for arr and ip2 is not.

Coming to the 2D arrays you are trying to use, you can define the arrays as:

uint8_t array1[WIDTH][HEIGHT];
uint8_t array2[WIDTH][HEIGHT];

You can define pointers to the arrays as:

uint8_t (*ip1)[HEIGHT] = array1;

or

uint8_t (*ip2)[WIDTH][HEIGHT] = &array1;

However, the first form is more natural. Assuming i and j are valid indices, you can use:

ip1[i][j] = 10;

just like you can use:

array1[i][j] = 10;

You will have to use a slightly different syntactic form to use ip2.

(*ip2)[i][j] = 10;

Hence, I would recommend using the first form of pointers to 2D arrays.

R Sahu
  • 204,454
  • 14
  • 159
  • 270
0

Now, I know that in C/C++, "an array is just a pointer to the first element of the array", but the compiler does not seem to agree.

It is right and you are most assuredly very wrong.

What is the magic declaration that will allow me to alias currState to nextState for the next iteration of my draw() method?

There is none. An array is just that- an array. It cannot be an alias. If you wish to swap the array contents, you'll have to swap each element yourself, or create a separate alias that you can rebind. Conceptually, this operation does not make sense. You cannot alias random things to random other things. This just demonstrates a lack of understanding of what an array is.

Note that this kind of shit is why boost::array was invented. Even if your compiler is too primitive to support boost, it's easy to re-create.

Puppy
  • 144,682
  • 38
  • 256
  • 465
-1
#define WIDTH 10
#define HEIGHT 12

#include <stdint.h>

uint8_t arr1[WIDTH][HEIGHT];
uint8_t arr2[WIDTH][HEIGHT];
uint8_t (*currState)[WIDTH][HEIGHT];
uint8_t (*nextState)[WIDTH][HEIGHT];

void copyArray(uint8_t ar1[WIDTH][HEIGHT], uint8_t ar2[WIDTH][HEIGHT]) {
    uint8_t i,j;
    for (i=0; i<WIDTH; i++) {
        for (j=0; j<HEIGHT; j++) {
            ar1[i][j] = ar2[i][j];
        }
    }
}

int main(void) {
    currState = &arr1;
    nextState = &arr2;

    uint8_t tempState[WIDTH][HEIGHT];

    copyArray(tempState, *currState);
    currState = nextState;
    nextState = &tempState;

    return 0;
}
user3817250
  • 1,003
  • 4
  • 14
  • 27
-3

Actually an array is a fixed pointer to the first element in the array (I think this is known as an L-value). This means your assignments at the end for the tempState, currState and nextState cannot work (as you have discovered).

After declaring space for the 2 2d-arrays, you could then declare pointers to the 2d arrays which you might be able to work in a reasonable way. To use the pointers for indexing into the 2-d array, the compiler would need to be aware of the size of the last dimension(s) of the array.

Richard C
  • 51
  • 1
  • 1
    L-value is a value which may reside on the left side of assignment. – cerkiewny Aug 07 '14 at 14:20
  • What do those declarations look like? I know "in theory" what I want to do (I tried what you suggested earlier) but I've got no idea what the declarations look like. – Robert Atkins Aug 07 '14 at 14:21
  • -1 since a) there are no examples, b) the first paragraph sentences are wrong (What strange definition of lvalue, isn't?), and c) the second paragraph directly says nothing. – Manu343726 Aug 07 '14 at 14:25
-4

Its been a while since I've done any C++, even longer since I've used 2-dimensional arrays and raw pointers! But, I believe the correct way is:

uint8_t currState[WIDTH][HEIGHT];
uint8_t (*ptr)[HEIGHT] = currState;

Edit -
My bad, should really have tested it first... the above has been corrected.

rtlayzell
  • 608
  • 4
  • 20
  • Doesn't work. `Cannot initialize a variable of type 'uint8_t **' (aka 'unsigned char **') with an lvalue of type 'uint8_t [36][20]'`. And it's not the indexing that's my problem. The compiler seems not to like me assigning a statically declared array to a pointer-y thing. – Robert Atkins Aug 07 '14 at 14:14
  • Thats not valid, since `**` is a pointer to a pointer (Not a pointer to an array) and `[][]` is an array of arrays. See [this](http://stackoverflow.com/questions/2895433/casting-char-to-char-causes-segfault) – Manu343726 Aug 07 '14 at 14:22
  • Thank you for pointing out the mistake, I have since corrected the error and tested it. – rtlayzell Aug 07 '14 at 14:42