Let's make a minor change to the code:
#include <iostream>
int main() {
double a = 5.0;
double *d = &a;
char *b = (char *)&a;
int a1 = 10;
for (int i = 0; i < sizeof(double); i++)
std::cout << std::hex << (int)b[i] << "\t";
}
This shows us the individual bytes of the double as they're stored in memory. The result I get is:
0 0 0 0 0 0 14 40
Now, if you look at the first four bytes of this, they're all zeros. Assuming your int is two or four bytes, when you try to view that memory as an int, the result is going to be zero, because all the non-zero bytes of the double are stored later in memory than the part you're looking at when using it as an int.
Of course, if you printed it out as a long long int instead, you'd get a non-zero result (a long long int is required to be at least 64 bits). Likewise, if you were doing this on a big-endian system, the 14 and 40 bytes of the double would probably be stored at the first bytes in memory rather than the last, so the result would again be non-zero.
The bottom line is that in this case, your cast is roughly equivalent to a reinterpret_cast. Rather than taking the double value and converting it to an int value, it's looking at the bytes of memory occupied by the double, and interpreting them as if they were an int.
Note that the result above isn't really required and you can't count on its happening in portable code. It is quite common and widely expected though (e.g., on most little-endian machines with IEEE floating point and 32-bit ints).