Proper way to call a c-function taking non-const pointer arguments, const_cast, reinterpret_cast, launder

Viewed 155

What is proper way to call a c-function taking non-const custom pointer arguments from c++?

Take, as a very common example, the function fftw_plan_dft_1d from FFTW3. http://fftw.org/fftw3_doc/Complex-DFTs.html#Complex-DFTs

fftw_plan fftw_plan_dft_1d(int n0,
                           fftw_complex *in, fftw_complex *out,
                           int sign, unsigned flags);

(fftw_complex is a typedef for double[2]).

Suppose I want apply this function to a couple of const-correct c++ containers.

std::vector<std::complex<double>> const In = {...};
std::vector<std::complex<double>> Out(In.size());

How should I do that?

_ First iteration, I have to extract the data pointer from the container,

assert(In.size() == Out.size());
fftw_plan fftw_plan_dft_1d(In.size(),
                           In.data(), Out.data(), // error const
                           FFTW_FORWARD, FFTW_ESTIMATE);

_ Second iteration

but since it is const I have to constcast. I assume that this is the only possible solution assuming that the reason for the C-interfact is that C doesn't have const arguments.

fftw_plan p = fftw_plan_dft_1d(In.size(),
                           const_cast<std::complex<double>*>(In.data()), // error std::complex is not convertible to fftw_complex
                           Out.data(), 
                           FFTW_FORWARD, FFTW_ESTIMATE);

_ Third iteration

Now, I have to convert std::complex<double> to fftw_complex (double[2]). Fortunately std::complex<double> is required to have the same layout as double[2].

fftw_plan p = fftw_plan_dft_1d(In.size(),
                           reinterpret_cast<fftw_complex*>(const_cast<std::complex<double>*>(In.data())), // is this UB?
                           reinterpret_cast<fftw_complex*>(Out.data()), 
                           FFTW_FORWARD, FFTW_ESTIMATE);

and now I am paranoid, apparently reinterpret_cast is always UB. I don't know how to use std::launder but I know that it can save reinterpret_cast UB in certain situations.

fftw_plan p = fftw_plan_dft_1d(In.size(),
                           std::launder(reinterpret_cast<fftw_complex*>(const_cast<std::complex<double>*>(In.data()))), // needs c++17
                           std::launder(reinterpret_cast<fftw_complex*>(Out.data())), 
                           FFTW_FORWARD, FFTW_ESTIMATE);

At the end of the day, is this a reasonable way to call a C-function that involves const and reinterpretation of types?

Am I too paranoid? or is it just that calling C from C++ is always formally UB in cases like these and I can't do anything about it?

1 Answers

I think you're indeed being quite paranoid, and I also think that's a good thing. Keep it up. A little paranoia will greatly reduce the number of times you shoot yourself in the foot!

You correctly identified the need to cast away the const qualifier because the library doesn't use const in its function signature. And you correctly identified the solution using const_cast<>.

You also correctly identified that reinterpret_cast<> is technically UB in this situation if you don't assume that fftw_complex is typedefed as double[2]. (I'm not familiar with FFTW3, so I don't know if that is even true or not, but you probably do.) If you know it's a typedef, it is not UB because the types are the same, just aliased under different names. If you don't know, it's "probably" still safe, but yes, I think that might be a case where you have to make a little leap of faith, knowing that any sane, real world compiler should do the right thing. There's a note to this effect in the FFTW3 documentation.

C++ has its own complex template class, defined in the standard header file. Reportedly, the C++ standards committee has recently agreed to mandate that the storage format used for this type be binary-compatible with the C99 type, i.e. an array T[2] with consecutive real [0] and imaginary [1] parts. (See report http://www.open-std.org/jtc1/sc22/WG21/docs/papers/2002/n1388.pdf WG21/N1388.) Although not part of the official standard as of this writing, the proposal stated that: “This solution has been tested with all current major implementations of the standard library and shown to be working.” To the extent that this is true, if you have a variable complex *x, you can pass it directly to FFTW via reinterpret_cast(x).

(Of course this layout guarantee is now part of the standard as of C++11.)

Finally, a note about C++ style casts. Everyone says you should use them instead of C casts, and this is true in most situations, but C-style casts are well-defined and the program isn't going to blow up if you use them. The tradeoff is one of conciseness and readable code (C-style) against explicit declaration of intent (C++-style). The exact rules for what the C++ compiler does with C-style casts are defined here. In my personal opinion, since you're already dealing with a C library with less-than-perfect function signatures, it is not the end of the world to simply C-style cast to a (double *) and call it a day.

Related