Way simpler: from inside out, read any subpart of the type as an expression. (Arrays have precedence over pointers, as usual.) The type that remains is that expression's type. So e.g. given the type:
const char *foo[][50]
the following expressions have the following types: foo -> const char *[][50]
foo[0] -> const char * [50]
foo[0][0] -> const char *
*foo[0][0] -> const char
Another example: int (*const bar)[restrict]
bar -> int (*const)[restrict]
*bar -> int [restrict]
(*bar)[0] -> int
One more: int (*(*f)(int))(void)
f -> int (*(*)(int))(void)
(*f) -> int (* (int))(void)
(*f)(0) -> int (* )(void)
*(*f)(0) -> int (void)
(*(*f)(0))() -> int
(In this case, the last expression is more readily written as f(0)(), since pointers to functions and functions are called using the same syntax.)