Reusing a float buffer for doubles without undefined behaviour

  • A+
Category:Languages

In one particular C++ function, I happen to have a pointer to a big buffer of floats that I want to temporarily use to store half the number of doubles. Is there a method to use this buffer as scratch space for storing the doubles, which is also allowed (i.e., not undefined behaviour) by the standard?

In summary, I would like this:

void f(float* buffer) {   double* d = reinterpret_cast<double*>(buffer);   // make use of d   d[i] = 1.;   // done using d as scratch, start filling the buffer   buffer[j] = 1.; } 

As far as I see there's no easy way to do this: if I understand correctly, a reinterpret_cast<double*> like this causes undefined behaviour because of type aliasing, and using memcpy or a float/double union is not possible without copying the data and allocating extra space, which defeats the purpose and happens to be costly in my case (and using a union for type punning is not allowed in C++).

It can be assumed the float buffer is correctly aligned for using it for doubles.

 


I think the following code is a valid way to do it (it is really just a small example about the idea):

#include <memory>  void f(float* buffer, std::size_t buffer_size_in_bytes) {     double* d = new (buffer)double[buffer_size_in_bytes / sizeof(double)];      // we have started the lifetime of the doubles.     // "d" is a new pointer pointing to the first double object in the array.             // now you can use "d" as a double buffer for your calculations     // you are not allowed to access any object through the "buffer" pointer anymore since the floats are "destroyed"            d[0] = 1.;     // do some work here on/with the doubles...       // conceptually we need to destory the doubles here... but they are trivially destructable      // now we need to start the lifetime of the floats again     new (buffer) float[10];         // here we are unsure about wether we need to update the "buffer" pointer to      // the one returned by the placement new of the floats     // if it is nessessary, we could return the new float pointer or take the input pointer     // by reference and update it directly in the function }  int main() {     float* floats = new float[10];     f(floats, sizeof(float) * 10);     return 0; } 

It is important that you only use the pointer you receive from placement new. And it is important to placement new back the floats. Even if it is a no-operation construction, you need to start the lifetimes of the floats again.

Forget about std::launder and reinterpret_cast in the comments. Placement new will do the job for you.

edit: Make sure you have proper alignment when creating the buffer in main.

Comment

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: