How does the range-based for work for plain arrays?

I think that the most important part of this question is, how C++ knows what the size of an array is (at least I wanted to know it when I found this question).

C++ knows the size of an array, because it's a part of the array's definition - it's the type of the variable. A compiler has to know the type.

Since C++11 std::extent can be used to obtain the size of an array:

int size1{ std::extent< char[5] >::value };
std::cout << "Array size: " << size1 << std::endl;

Of course, this doesn't make much sense, because you have to explicitly provide the size in the first line, which you then obtain in the second line. But you can also use decltype and then it gets more interesting:

char v[] { 'A', 'B', 'C', 'D' };
int size2{ std::extent< decltype(v) >::value };
std::cout << "Array size: " << size2 << std::endl;

It works for any expression whose type is an array. For example:

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}};
for(int &n : *arraypointer)
  n *= 2;
delete [] arraypointer;

For a more detailed explanation, if the type of the expression passed to the right of : is an array type, then the loop iterates from ptr to ptr + size (ptr pointing to the first element of the array, size being the element count of the array).

This is in contrast to user defined types, which work by looking up begin and end as members if you pass a class object or (if there is no members called that way) non-member functions. Those functions will yield the begin and end iterators (pointing to directly after the last element and the begin of the sequence respectively).

This question clears up why that difference exists.