Search code examples
c++arrayspointersreferencereinterpret-cast

Converting from pointer to c-array reference?


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


Solution

  • 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.