2

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?

alfC
  • 14,261
  • 4
  • 67
  • 118
  • 2
    C [*does* have `const`](https://en.cppreference.com/w/c/language/const) (since C89/C90) but some poorly written libraries do not use it in their function prototypes. – TypeIA Nov 13 '18 at 00:07
  • @TypeIA, I noticed that, what should one do in those cases? break const correctness from the beginning or use `const_cast`. To be clear in this case, the only reason I know that I can pass a const pointer to this library is because of the last parameter `FFTW_ESTIMATE` which promises not overwrite the data of first pointer. – alfC Nov 13 '18 at 00:11

1 Answers1

4

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.

TypeIA
  • 16,916
  • 1
  • 38
  • 52
  • `fftw_complex` is a typedef of `double[2]` the problem is that I am reinterpreting from `std::complex&` to `double[2]&`. `std::complex` is indeed now mandated to have `double[2]` layout, what I don't know if that saves `reinterpret_cast` from being UB, cppreference seems to say that reinterpret is ok here (at least for refences, doesn't say anything about pointers) https://en.cppreference.com/w/cpp/numeric/complex. – alfC Nov 13 '18 at 01:25
  • I am tempted from you answer to say that all the paranoia of `std::launder(reinterpret_cast(const_cast*>(in)))` can be simply replaced by `(fftw_complex*)in`, because I am using C-library anyway. Good. (BTW there is no cast to `(double*)` involved I think.) – alfC Nov 13 '18 at 01:29
  • It means `reinterpret_cast` isn't needed at all, because given `p = &c[0]`, `p` and `p + 1` are guaranteed to be addresses of valid `double`s and that's all you need (minus casting away the `const`). I agree that `std::launder()` seems like overkill and personally I would probably write `const_cast(&c[0][0])` but I see nothing wrong with the C-style cast version. – TypeIA Nov 13 '18 at 01:43
  • Also maybe worth observing that the layout guarantees of `std::vector` come into play here too, such that given `std::vector v`, you can safely assume that the double components of each complex value are all packed at consecutive memory addresses. – TypeIA Nov 13 '18 at 01:44
  • Thanks, you say that `reinterpret_cast` is not needed at all, but how would you call `fftw_plan_dft_1d` on `std::vector>` without using `reinterpret_cast` or c-style cast? I don't see how. – alfC Nov 13 '18 at 03:41
  • I thought `std::complex` had an `operator[]` that returned a `T&` but it does not, so you are correct and a `reinterpret_cast<>` is needed. – TypeIA Nov 13 '18 at 13:39
  • `launder` is implicit when you cross an ABI defined call, that is when you call a separately compiled code – curiousguy Nov 16 '18 at 22:06