I believe the reason Square (* grid)[]; works is because pointers to incomplete types are allowed, and an array without a size counts as an incomplete type.
The reason that you can't do
Square (* grid)[] = new Square[width * height];
even though it looks like the types match up perfectly is just another manifestation of the bug in the design of C where array types are treated specially. It seems like newing an object of type Square[] should return a pointer to an object of that type. However you're not really newing an array type, but new[]ing the element type Square. The result of new[] is a pointer to the element type, in line with the C convention for arrays.
You can use a cast to 'fix' this to use the 'right' type:
// pretend array types behave rationally
Square (* grid)[] = (Square (*)[]) new Square[width * height];
(*grid)[3] = 10;
// the above is equivalent to the following
Square *grid = new Square[width * height];
grid[3] = 10;
Or you can just do it the C++ way and use a std::vector
std::vector<Square> grid(width * height);
If you have a fixed size array you can use std::array
std::array<Square,10> *grid = new std::array<Square,10>;
std::array pretty much fixes all the mistakes made in the design of array types. For example std::array can't 'forget' its size, functions can take std::array parameters by value (whereas with raw arrays the array syntax just becomes a synonym for pointers), functions can return std::array whereas they are inexplicably prohibited from returning arrays (The syntax would be int foo()[3];), and with std::array there's no need for a special array allocator new[] which must be matched to an array deallocator delete[] (instead you can say foo = new std::array<int,3> and then 'delete foo;')
Square (* grid)[];toSquare grid[];? Ifgridis truly a grid then there's no point to adding the complexity of making it multi-dimensional.Square grid[];andSquare* grid;are identical...