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 adouble[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 fromdouble[4]todouble[2][2]. Then you are good to go, assuming you use the return value fromstd::start_lifetime_as(otherwise astd::launderis needed).This would not generally work though if the
double[4]array was a subobject of another object or if it was aconstcomplete object with automatic/static/thread storage duration. The preconditions ofstd::start_lifetime_asmust be satisfied. It is not possible to use it to avoid thestd::launderreachability conditions which make it impossible to ever reach any memory other than that of the fourdoublein the array from a pointer to one of its elements.Also,
std::start_lifetime_asreplaces 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 whetherdelete[] data;would be ok. The language in [expr.delete] doesn't seem very clear to me.