Quick A: a pointer has no size information.
Recently on SO:
The type of row is deduced to be int*. That means, just like any other int*, the compiler doesn't know how big the array it points to is, or even that it's a pointer to the first element of an array at all. All of that information is lost when an array decays into a pointer to its first element.
If instead you use something likefor (auto& row : ia) //<-- NOTE: row is now a reference for (int* j = std::begin(row); j != std::end(row); ++j) std::cout << *j << '\n';
then the type of row will be deduced to int (&): reference to an array of 4 ints. The length information is retained, so std::begin and std::end have the information they need.
PS: Just as a note: range-for works by using std::begin and std::end internally, so the above can be a bit more concisely written asfor (auto& row : ia) for (auto j : row) std::cout << j << '\n';