Except when it is the operand of the sizeof or unary & operator, or is a string literal being used to initialize another array in a declaration, an expression of type "N-element array of T" will be converted ("decay") to an expression of type "pointer to T", and the value of the expression will be the address of the first element in the array.
In main, the type of the expression arr in the function call foo(arr) is "2-element array of 2-element array of char"; since it isn't the operand of the sizeof or unary & operators, it "decays" to an expression of type "pointer to 2-element array of char", or char (*)[2].
Thus, the parameter temp is type char (*)[2], and it points to the first element of arr. The parameter declaration char temp[][2] is equivalent to char (*temp)[2]. The expression *temp is equivalent to temp[0], and both have type "2-element array of char" (char [2]). The expression temp + 1 gives you the address of the next 2-element array of char, so *(temp + 1) is equivalent to temp[1].
Here's a table to summarize all of that:
Expression Type Decays To Value
---------- ---- --------- -----
arr char [2][2] char (*)[2] &arr[0][0]
*arr char [2] char * arr[0]
&arr char (*)[2][2] n/a &arr[0][0]
arr[i] char [2] char * &arr[i][0]
*arr[i] char n/a arr[i][0]
&arr[i] char (*)[2] n/a &arr[i][0]
arr[i][j] char n/a arr[i][j]
temp char (*)[2] n/a &arr[0][0]
*temp char [2] char * arr[0]
&temp char (**)[2] n/a addr of temp variable
temp[i] char [2] char * &arr[i][0]
*temp[i] char n/a arr[i][0]
&temp[i] char (*)[2] n/a &arr[i][0]
temp[i][j] char n/a arr[i][j]
arr + 1 char [2][2] char (*)[2] &arr[1][0]
*(arr + 1) char [2] char * arr[1]
temp + 1 char (*)[2] n/a &arr[1][0]
*(temp + 1) char [2] char * arr[1]
arr[0][0] char n/a 'a'
arr[0][1] char n/a 'b'
arr[1][0] char n/a 'c'
arr[1][1] char n/a 'd'
**temp char n/a 'a'
*temp[0] char n/a 'a'
temp[0][0] char n/a 'a'
**(temp + 1) char n/a 'c'
*temp[1] char n/a 'c'
temp[1][0] char n/a 'c'
So, in your print statement, you would write either
printf("%c", **temp); // temp == &arr[1][0] after temp++
or
printf("%c", *temp[0]); // temp[0] == arr[1] after temp++
or
printf("%c", temp[0][0]); // temp[0][0] == arr[1][0] after temp++
The expressions arr, &arr, *arr, arr[0], &arr[0], *arr[0], and &arr[0][0] all yield the same value - the address of the first element of arr (remember that the address of the array and the address of the first element of the array are the same). They just differ in type.
Note that &temp gives us a different value than &arr. Since temp was declared as a pointer, not an array, &temp gives us the address of the pointer variable.
A picture may help:
+---+
arr: |'a'| arr[0][0] <------------------+ before temp++
+---+ |
|'b'| arr[0][1] |
+---+ |
|'c'| arr[1][0] <--+ after temp++ |
+---+ | |
|'d'| arr[1][1] | |
+---+ | |
... | |
+---+ | |
temp: | |---------------+---------------+
+---+
-Wall. Don't ignore warnings.warning: format ‘%c’ expects argument of type ‘int’, but argument 2 has type ‘char *’ [-Wformat=]andwarning: missing braces around initializer [-Wmissing-braces]. Are you sure you get no warnings?