Is it possible to convert allocated memory into a reference to c-array without invoking undefined behavior?
I have 4 elements in the heap and want to see it as a 2x2 c-array (to pass to a function for example).
I use reinterpret_cast
here. Is there something else, less drastic than reinterpret_cast
that I can do?
// Type your code here, or load an example.
double f(double(&ref)[2][2]) {
return ref[0][0] + ref[1][1];
}
int main() {
double* data = new double[4];
data[0] = 1; data[1] = 1; data[2] = 1; data[3] = 1;
auto& array = reinterpret_cast<double(&)[2][2]>(*data);
// auto& array = *reinterpret_cast<double(*)[2][2]>(data);
f(array);
delete[] data;
}
https://godbolt.org/z/84Kxjoqjq
clang-tidy recommend plainly not using reinterpret_cast
.
Sonar Lint says that this use of reinterpret_cast
is undefined behavior.
dereference of type 'double *[2][2]' that was reinterpret_cast from type 'double *' has undefined behavior
Yes, that's undefined behavior and strictly by the standard, there is no way to do what you want without UB.
You either have a double[4]
or a double[2][2]
. The memory can't hold both in their lifetime at the same time and treating it as if it does will cause undefined behavior.
There was some proposal to add certain aliasing permissions of this kind to the standard, at least when wrapping in a class type via explicit layout annotations. See P1912. However that proposal doesn't seem to have seen any progress since 2020.
Of course, a std::start_lifetime_as
(C++23) could be used to change the type of the object at the memory location that is in its lifetime from double[4]
to double[2][2]
. Then you are good to go, assuming you use the return value from std::start_lifetime_as
(otherwise a std::launder
is needed).
This would not generally work though if the double[4]
array was a subobject of another object or if it was a const
complete object with automatic/static/thread storage duration. The preconditions of std::start_lifetime_as
must be satisfied. It is not possible to use it to avoid the std::launder
reachability conditions which make it impossible to ever reach any memory other than that of the four double
in the array from a pointer to one of its elements.
Also, std::start_lifetime_as
replaces the object. The old object is not in its lifetime anymore afterwards. Therefore you can't use this if anyone relies on that still being the case. E.g. it isn't claer whether delete[] data;
would be ok. The language in [expr.delete] doesn't seem very clear to me.