UPDATE: For C ++ only , to scroll C down. In short, there is no UB in C ++ and there is UB in C.
8.3.4/7 says:
For multidimensional arrays, a consistent rule applies. If E is an n-dimensional array of rank i xj x ... xk, then E appearing in the expression that is to be converted from array to pointer (4.2) is converted to a pointer to an (n - 1) -dimensional array with rank j x ... x k. If the * operator explicitly or implicitly as a result of the signature is applied to this pointer, the result is a directional (n - 1) -dimensional array, which itself is immediately converted to a pointer.
Thus, this will not result in an error in C ++ (and will work as expected):
T *p2 = **pa2; T *p3 = ***pa3;
Regarding whether it is UB or not. Consider the first conversion:
T(*pa1)[6] = (T(*)[6])a;
In C ++, this is actually
T(*pa1)[6] = reinterpret_cast<T(*)[6]>(a);
And here is what the standard says about reinterpret_cast :
An object pointer can be explicitly converted to an object pointer of another type. When a v value of type "pointer to T1" is converted to type "pointer to cv T2", the result is static_cast <summary T2 *> (static_cast <cv void *> (v)) if both T1 and T2 are standard layout types (3.9 ) and alignment, the requirements of T2 are not more stringent than the requirements of T1, or if any type is invalid.
So, a converted to pa1 via static_cast to void* and vice versa. It is guaranteed that the static tide void* will return the real address of address a , as specified in 4.10/2 :
A value of type "pointer to cv T", where T is an object type, can be converted to a pointer of type "pointer" to cv void. The result of converting the value of a non-zero pointer to a pointer to an object type is a "pointer to cv void" represents the address of the same byte in memory, as the original value of the pointer.
The following static cast to T(*)[6] again is guaranteed to return the same address as in 5.2.9/13 :
A value of type "pointer to cv1 void" can be converted to a prvalue of type "pointer to cv2 T", where T is the type of object and cv2 is the same cv-qualification as or a higher cv-qualification than cv1. The null pointer value is converted to the null pointer value for the destination type. If the initial value of the pointer represents the address of A byte in memory and A satisfies the alignment requirement T, then the resulting pointer value represents the same address as the original value of the pointer, that is, A
Thus, pa1 guaranteed to point to the same byte in memory as a , and therefore access to the data through it is absolutely right, since the alignment of arrays coincides with the alignment of the base type.
How about C?
Consider again:
T(*pa1)[6] = (T(*)[6])a;
C11 6.3.2.3/7 states the following:
A pointer to an object type can be converted to a pointer to another object type. If the resulting pointer is not correctly aligned for the reference type, the behavior is undefined. Otherwise, with the opposite, the result will be compared with the original pointer. When a pointer to an object is converted to a pointer to a character type, the result points to the low address byte of the object. Successive increments of the result, up to the size of the object, prints pointers to the remaining bytes of the object.
This means that if the conversion does not match char* , the value of the converted pointer will not be guaranteed equal to the value of the original pointer , which will lead to undefined behavior when accessing data through the converted pointer. To make it work, the conversion must be done explicitly via void* :
T(*pa1)[6] = (T(*)[6])(void*)a;
Transitions back to T *
T *p = a; T *p1 = *pa1; T *p2 = **pa2; T *p3 = ***pa3;
All these are transformations from array of T to pointer to T , which are valid both in C ++ and C, and no UB is started by accessing data through converted pointers.