In C++, it's legal for a char, unsigned char, or std::byte pointer to alias any T pointer. Such aliased pointer is capable of accessing the object's representation in raw memory.
My question is whether it's legal and free of undefined-behavior to use a T* pointer derived from arithmetic on a char/unsigned char/std::byte pointer aliasing an original T array object sequence -- provided that the resultant pointer is still aligned correctly and within the original reachability of the aliased sequence.
As a concrete example:
// Array sequence is 2 in length
auto array = std::array<unsigned,2>{0xdead, 0xbeef};
// Derive the address of &array[1] using std::byte pointer
auto p = reinterpret_cast<std::byte*>(array.data());
auto p1 = reinterpret_cast<unsigned*>(p + sizeof(unsigned));
assert(p1 == &array[1]); // This expression should be legal since the pointer is safely derived
assert(*p1 == array[1]); // But is this legal, as per the C++ standard?
The pointer p1 should be safely derived in the above example since it doesn't exceed the reachability of array's bound. I know that in many cases in C++ it's legal to cast a pointer to a type/representation that can't actually be used/dereferenced. Is the resultant pointer here legal to dereference as far as the standard is concerned?
For the purpose of this question, assume that T may not be standard-layout type.
Note: I'm not asking about whether this will work in practice, since I have yet to see this type of code fail on compilers; but I'm curious to know whether this is, strictly-speaking, legal per the C++ standard.
Edit:
I'd rather not derail this question, but there are a few comments that are asserting that p + sizeof(unsigned) is undefined behavior as a result of [expr.add], in particular quoting both [expr.add]/4 and [expr.add]/6 due to p not actually being a byte-array.
There is, in fact, a byte sequence[1], and both [expr.add]/4 and [expr.add]/6 simply do not apply[2]
[1]: char, unsigned char, and std::byte view the object representation of an object, which is defined under [basic.types.general]/4 as being:
The object representation of an object of type
Tis the sequence ofNunsigned charobjects taken up by the object of typeT, whereNequalssizeof(T).
The above means that there is a formal sequence of unsigned char (which std::byte is capable of representing), which means p + sizeof(unsigned) is valid. We aren't viewing through a similar pointer, we are viewing exactly as the underlying object-representation which is legal.
[2]: [expr.add]/4 is simply referring to reachability of array objects. However, as pointed out above, there is a valid and reachable object due to the unsigned char sequence, and we do not exceed reachability -- thus this doesn't apply as a case of undefined behavior.
As far as I can tell, [expr.add]/6 primarily exists to avoid cases where an array of derived types is cast to a base pointer and attempted to be indexed. However it wouldn't apply in this case because we aren't viewing through a "similar" pointer, we are viewing an object representation that is formally defined as a sequence of unsigned chars. This isn't a similar representation, it's exact.