I'm building a dynamic animation & rendering system and I would like to use Boost.Units for representing physical quantities to get the nice dimensional safety. However, I will have to pass arrays of quantities around to functions which know nothing about Boost, such as:
OpenGL buffer-filling commands. These simply take a const void *
and expect to find an array of either float
or double
values when dereferencing it. They read the data.
Linear algebra functions (such as gemm
or gesv
) from different implementations of BLAS and LAPACK. These generally take either a float *
or double *
to a given array. They both read and write to the data.
I know that boost::units::quantity<U, T>
has a const T& value()
member which gives direct reference access to the contained T
value. I have also verified that a boost::units::quantity<U, T>
is a standard-layout struct with exactly one non-static data member, of type T
.
So, let's assume that for a boost::units::quantity<U, T> q
, the following holds:
static_cast<const void*>(&q) == static_cast<const void*>(&q.value())
sizeof(q) == sizeof(T)
My question is: given an array boost::units::quantity<U, T> a[100];
, is it safe to:
Pass &a[0].value()
to a function which expects to read an array of 100 objects of type T
at the address?
Pass reinterpret_cast<T*>(&a[0])
to a function which will write 100 sequential values of type T
at the address?
I am well aware this is probably Undefined Behaviour, but right now I have to follow the "Practicality beats purity"(1) principle. Even if this is UB, is it one which will do the expected thing, or will it bite in unforeseen ways? Since this might be compiler-specific: I need this for modern MSVC (from VS 2015).
And if this is not safe, is there a way to actually do this safely? With "this" referring to "using Boost.Units with OpenGL and with number crunchers which only have a C interface," without unnecessarily copying data.
(1) Adapted from the Zen of Python.
Yes, this looks like something you can do.
There's one thing you didn't mention and should be added to the list of conditions to check, though: the alignment of the wrapped amount type should match that of the underlying type. (see alignof
).
So, in practice I'd write code like this only with a number of static_asserts¹ that guard the assumptions that make the re-interpretation valid.
If you add the assertion that T is the same as remove_cv_t<decltype(q.value())>
this should be reliable.
With these pre-cautions in place there should not be UB, just IB (implementation defined behaviour) due the semantics of reinterpret_cast on your particular platform.
¹ and perhaps the debug assert that &q.value() == &q
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With