A variable has a number of distinct properties.
Given your variable definition, which I quote here
int num = 5;
its type is int, its name is num, it is initialised with a value of 5, and its address (e.g. location in machine memory) is &num.
If I was to define a second variable immediately after
int q = 5
then this variable would (like num) have type int, and value 5, but a different name q and a different address in memory &q. Even though they have the same value, the addresses of num and q are different. That (among other things) ensures that assigning q = 6 does not change the value of num.
It goes further. The pointer in your sample
int * p;
means that p is a pointer to an int. It has type int *, so its value can be the address of an int. So the assignment
p = num; //This results in an error
doesn't work, because p has type int * and num has type int. The two types are distinct, and an int cannot be converted implicitly to an int *, so the assignment is not allowed. They are different types. In the real world, the street address of a house is something different from a house, even if there is an association between them.
However your assignment
p = # //This does not
works, because p is of type int * and &num is the address of an int so also has type int *.
However, with arrays, the rules are a little different.
int num[2] = {5, 10};
p = num; //This does not result in an error
p = # //This does
Firstly, num is an array of two int. It has name num, but its type is int[2], and its value is actually based on the pair of ints 5 and 10.
When used in an expression, num is converted to a pointer to its first element. So
p = num;
is equivalent to
p = &num[0];
The conversion of num to &num[0] is called an "array to pointer" conversion.
The second assignment you tried
p = # //This does
is different. In this case &num is the address of the array num, not the address of its first element. So &num has type int (*)[2] which (in C++) syntactically means "pointer to an array of two int".
The type of p is int * (because that's how you defined it) whereas &num gies a result with type int (*)[2]. Those types are different, so the assignment is invalid.
If you want a variable that points to an array of two int (as distinct from your p which can point at the first element of an array of int) then define
int num[2] = {5,10};
int (*q)[2]; // q has type int (*)[2]
q = num; // this will work
Then you can do, for example;
std::cout << (*q)[0] << ' ' << (*q)[1] << '\n';
to print the values 5 and 10. This works because q points at the array, *q is (a reference to) the array num, so (*q)[0] and (*q)[1] access num[0] and num[1] respectively.
Note that the parentheses () are critical in all of the above discussion. *q[0] is actually (by rules of operator precedence and associativity) equivalent to *(q[0]) which is something quite different from (*q)[0]. I'll leave working out what *(q[0]) is (or is not) an exercise.