Reputation: 1
I have a pointer T * pValues
that I would like to view as a T (&values)[N]
In this SO answer https://stackoverflow.com/a/2634994/239916, the proposed way of doing this is
T (&values)[N] = *static_cast<T(*)[N]>(static_cast<void*>(pValues));
The concern I have about this is. In his example, pValues
is initialized in the following way
T theValues[N];
T * pValues = theValues;
My question is whether the cast construct is legal if pValues
comes from any of the following constructs:
T theValues[N + M]; // M > 0
T * pValues = theValues;
T * pValues = new T[N + M]; // M >= 0
Upvotes: 20
Views: 6653
Reputation: 76809
Since C++17 at least the shown expression isn't safe, even if pValues
is a pointer to the first element of the array and the array is of exactly matching type (including excat size), whether obtained from a variable declaration or a call to new
. (If theses criteria are not satisfied it is UB regardless of the following.)
Arrays and their first element are not pointer-interconvertible and therefore reinterpret_cast
(which is equivalent to two static_casts
through void*
) cannot cast the pointer value of one to a pointer value of the other.
Consequently static_cast<T(*)[N]>(static_cast<void*>(pValues))
will still point at the first element of the array, not the array object itself.
Derferencing this pointer is then undefined behavior, because of the type/value mismatch.
This can be potentially remedied with std::launder
, which may change the pointer value where reinterpret_cast
can't. Specifically the following may be well-defined:
T (&values)[N] = *std::launder(static_cast<T(*)[N]>(static_cast<void*>(pValues)));
or equivalently
T (&values)[N] = *std::launder(reinterpret_cast<T(*)[N]>(pValues));
but only if the pointer that would be returned by std::launder
cannot be used to access any bytes that weren't accessible through the original pValues
pointer. This is satified if the array is a complete object, but e.g. not satisfied if the array is a subarray of a two-dimensional array.
For the exact reachability condition, see https://en.cppreference.com/w/cpp/utility/launder.
Upvotes: 2
Reputation: 42103
Short answer: You are right. The cast is safe only if pValues
is of type T[N]
and both of the cases you mention (different size, dynamically allocated array) will most likely lead to undefined behavior.
The nice thing about static_cast
is that some additional checks are made in compile time so if it seems that you are doing something wrong, compiler will complain about it (compared to ugly C-style cast that allows you to do almost anything), e.g.:
struct A { int i; };
struct C { double d; };
int main() {
A a;
// C* c = (C*) &a; // possible to compile, but leads to undefined behavior
C* c = static_cast<C*>(&a);
}
will give you: invalid static_cast from type ‘A*’ to type ‘C*’
In this case you cast to void*
, which from the view of checks that can be made in compile time is legal for almost anything, and vice versa: void*
can be cast back to almost anything as well, which makes the usage of static_cast
completely useless at first place since these checks become useless.
For the previous example:
C* c = static_cast<C*>(static_cast<void*>(&a));
is no better than:
C* c = (C*) &a;
and will most likely lead to incorrect usage of this pointer and undefined behavior with it.
In other words:
A arr[N];
A (&ref)[N] = *static_cast<A(*)[N]>(&arr);
is safe and just fine. But once you start abusing static_cast<void*>
there are no guarantees at all about what will actually happen because even stuff like:
C *pC = new C;
A (&ref2)[N] = *static_cast<A(*)[N]>(static_cast<void*>(&pC));
becomes possible.
Upvotes: 10