Правильный способ вызова c-функции, принимающей аргументы неконстантного указателя, const_cast, reinterpret_cast ,андер - PullRequest
0 голосов
/ 13 ноября 2018

Как правильно вызвать функцию c, принимающую неконстантные аргументы пользовательских указателей из c ++?

Возьмем в качестве очень распространенного примера функцию fftw_plan_dft_1d из 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 - это typedef для double[2]).

Предположим, я хочу применить эту функцию к паре const-правильных контейнеров c ++.

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

Как мне это сделать?

_ Первая итерация, я должен извлечь указатель данных из контейнера,

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

_ Вторая итерация

но так как это const, я должен constcast. Я полагаю, что это единственно возможное решение, если предположить, что причиной C-взаимодействия является то, что у C нет аргументов const.

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);

_ Третья итерация

Теперь я должен преобразовать std::complex<double> в fftw_complex (double[2]). К счастью, std::complex<double> должен иметь ту же раскладку, что и 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);

и теперь я параноик, очевидно, reinterpret_cast всегда UB. Я не знаю, как использовать std::launder, но я знаю, что он может сэкономить reinterpret_cast UB в определенных ситуациях.

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);

В конце концов, является ли это разумным способом вызова C-функции, которая включает в себя const и реинтерпретацию типов?

Я слишком параноик? или это просто, что вызов C из C ++ всегда формально UB в подобных случаях, и я ничего не могу с этим поделать?

1 Ответ

0 голосов
/ 13 ноября 2018

Я думаю, что вы действительно параноик, и я тоже думаю, что это хорошо.Так держать.Небольшая паранойя значительно сократит количество случаев, когда вы стреляете себе в ногу!

Вы правильно определили необходимость отбрасывать квалификатор const, поскольку библиотека не использует const в своей сигнатуре функции,И вы правильно определили решение, используя const_cast<>.

Вы также правильно определили, что reinterpret_cast<> технически UB в этой ситуации , если вы не предполагаете, что fftw_complex определен как double[2].(Я не знаком с FFTW3, поэтому я не знаю, правда ли это или нет, но вы, вероятно, знаете.) Если вы знаете, что это typedef, это не UB, потому что типы одинаковы, просто псевдоним подразные имена.Если вы не знаете, это "вероятно" все еще безопасно, но да, я думаю, что это может быть случай, когда вам нужно сделать небольшой прыжок веры, зная, что любой здравомыслящий компилятор реального мира долженсделать правильную вещь.В документации по FFTW3 имеется примечание об этом .

C ++ имеет собственный сложный класс шаблонов, определенный в стандартном заголовочном файле.Как сообщается, комитет по стандартам C ++ недавно согласился поручить, чтобы формат хранения, используемый для этого типа, был двоично-совместимым с типом C99, то есть массивом T [2] с последовательными действительными [0] и мнимыми [1] частями.(См. Отчет http://www.open -std.org / jtc1 / sc22 / WG21 / docs / paper / 2002 / n1388.pdf WG21 / N1388.) Хотя это и не является частью официального стандарта на момент написания,В предложении указывалось, что: «Это решение было протестировано со всеми существующими основными реализациями стандартной библиотеки и показано, что оно работает». Если это действительно так, если у вас есть переменная complex * x, вы можете передать ее непосредственно в FFTWvia reinterpret_cast (x).

(Конечно, эта гарантия макета является теперь частью стандарта C ++ 11.)

Наконец, заметка о приведениях в стиле C ++.Все говорят, что вы должны использовать их вместо приведения C, и это верно в большинстве случаев, но приведения в стиле C четко определены, и программа не взорветсяесли вы используете их.Компромисс заключается в краткости и удобочитаемости кода (в стиле C) против явного объявления намерения (в стиле C ++).Точные правила того, что компилятор C ++ делает с приведениями в стиле C , определены здесь .По моему личному мнению, поскольку вы уже имеете дело с библиотекой C с неидеальными сигнатурами функций, это не конец света, просто приведение в стиле C к (double *) и вызов это день.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...