使用std::rotate处理fftw_complex数据报错,求解决方案
问题背景
使用C的FFTW3库处理软件无线电捕获数据,FFT/IFFT功能正常,但实现fftShift时调用std::rotate处理fftw_complex数组出现编译错误。编译命令为g++ CppFFTW.cpp -lfftw3 -lm,编译器版本为g 9.4.0。
错误信息如下:
In file included from /usr/include/c++/9/algorithm:62, from CppFFTW.cpp:13: /usr/include/c++/9/bits/stl_algo.h: In instantiation of ‘_RandomAccessIterator std::_V2::__rotate(_RandomAccessIterator, _RandomAccessIterator, _RandomAccessIterator, std::random_access_iterator_tag) [with _RandomAccessIterator = double (*)[2]]’: /usr/include/c++/9/bits/stl_algo.h:1449:27: required from ‘_FIter std::_V2::rotate(_FIter, _FIter, _FIter) [with _FIter = double (*)[2]]’ CppFFTW.cpp:76:54: required from here /usr/include/c++/9/bits/stl_algo.h:1371:16: error: array must be initialized with a brace-enclosed initializer 1371 | _ValueType __t = _GLIBCXX_MOVE(*__p); | ^~~ /usr/include/c++/9/bits/stl_algo.h:1373:22: error: invalid array assignment 1373 | *(__p + __n - 1) = _GLIBCXX_MOVE(__t); | ^ /usr/include/c++/9/bits/stl_algo.h:1394:16: error: array must be initialized with a brace-enclosed initializer 1394 | _ValueType __t = _GLIBCXX_MOVE(*(__p + __n - 1)); | ^~~ /usr/include/c++/9/bits/stl_algo.h:1396:10: error: invalid array assignment 1396 | *__p = _GLIBCXX_MOVE(__t); | ^
fftShift功能是将零分量移至数组中心:
- 元素个数为奇数时:
{a, b, c, d, e, f, g} -> {e, f, g, a, b, c, d} - 元素个数为偶数时:
{a, b, c, d, e, f, g, h} -> {e, f, g, h, a, b, c, d}
原错误实现代码:
// FFT shift for complex data void fftShift(fftw_complex *data) { // even number of elements if (N % 2 == 0) { std::rotate(&data[0], &data[N >> 1], &data[N]); } // odd number of elements else { std::rotate(&data[0], &data[(N >> 1) + 1], &data[N]); } }
问题解答
1. 为何教程中可用,我这里报错?
FFTW3的fftw_complex本质是C风格数组类型(定义为typedef double fftw_complex[2];),而C标准库算法如std::rotate要求迭代器指向的类型是可移动赋值的对象。数组类型无法直接被移动或赋值(C中数组不能作为左值赋值,也不能用单个对象初始化数组),这是C++的基础语法规则。
教程能运行的可能性:
- 教程使用的FFTW版本可能将
fftw_complex定义为结构体而非数组(部分旧版本或特殊编译配置下可能如此); - 教程可能隐式转换为
std::complex<double>后调用std::rotate,但未明确写出转换步骤。
2. 如何理解错误信息?
错误核心是数组类型不支持std::rotate内部的移动赋值操作:
error: array must be initialized with a brace-enclosed initializer:std::rotate尝试用单个数组元素初始化临时数组变量,但C++要求数组必须用大括号列表初始化,不能直接用另一个数组赋值;error: invalid array assignment:std::rotate尝试将临时数组赋值给目标数组位置,但C++不允许直接赋值数组(数组不是可修改的左值)。
3. 如何实现可用的fftShift函数?
有两种可靠实现方式:
方式一:利用std::complex<double>类型转换
std::complex<double>与fftw_complex内存布局完全兼容(都是连续的两个double,分别存储实部和虚部),将fftw_complex*强制转换为std::complex<double>*后调用std::rotate:
// 针对std::complex<double>的fftShift实现 void fftShift(std::complex<double>* data) { static_assert(sizeof(fftw_complex) == sizeof(std::complex<double>), "fftw_complex and std::complex<double> must have the same size"); if(N % 2 == 0) { std::rotate(data, data + (N >> 1), data + N); } else { std::rotate(data, data + (N >> 1) + 1, data + N); } } // 针对fftw_complex的重载,完成类型转换 void fftShift(fftw_complex* data) { fftShift(reinterpret_cast<std::complex<double>*>(data)); }
static_assert用于编译期验证两种类型的内存布局一致性,避免因FFTW配置变化导致的未定义行为。
方式二:手动实现旋转逻辑
利用“三次反转等价于旋转”的算法,直接操作fftw_complex数组,无需类型转换:
void fftShift(fftw_complex* data) { int mid = (N % 2 == 0) ? (N >> 1) : (N >> 1) + 1; // 三次反转实现旋转:rotate(a, m, b) = reverse(a,m) + reverse(m,b) + reverse(a,b) std::reverse(data, data + mid); std::reverse(data + mid, data + N); std::reverse(data, data + N); }
4. 是否需要更换编译器版本?
不需要。问题根源是fftw_complex的数组类型与std::rotate的要求不兼容,这是C++语言的固有规则,与编译器版本无关。即使升级编译器,错误依然会存在。
修正后的完整代码
/* Example code for how to utilize the FFTW libs Adapted from damian-dz C++ Tutorial To compile, run: g++ CppFFTW.cpp -lfftw3 -lm -lfftw3 -lm links the code to the fftw3 library */ #include <fftw3.h> #include <iostream> #include <cmath> #include <algorithm> #include <complex> // macros for real & imaginary parts #define REAL 0 #define IMAG 1 // length of the complex arrays #define N 8 /* Computes the 1-D Fast Fourier Transform. */ void fft(fftw_complex *in, fftw_complex *out) { // create a DFT plan fftw_plan plan = fftw_plan_dft_1d(N, in, out, FFTW_FORWARD, FFTW_ESTIMATE); // execute the plan fftw_execute(plan); // clean up fftw_destroy_plan(plan); fftw_cleanup(); } /* Displays complex numbers in the form a +/- bi */ void displayComplex(fftw_complex *y) { for (int idx = 0; idx < N; ++idx) { if (y[idx][IMAG] < 0) { std::cout << y[idx][REAL] << " - " << abs(y[idx][IMAG]) << "i" << std::endl; } else { std::cout << y[idx][REAL] << " + " << abs(y[idx][IMAG]) << "i" << std::endl; } } } // 针对std::complex<double>的fftShift实现 void fftShift(std::complex<double>* data) { static_assert(sizeof(fftw_complex) == sizeof(std::complex<double>), "fftw_complex and std::complex<double> must have the same size"); if(N % 2 == 0) { std::rotate(data, data + (N >> 1), data + N); } else { std::rotate(data, data + (N >> 1) + 1, data + N); } } // 针对fftw_complex的重载,完成类型转换 void fftShift(fftw_complex* data) { fftShift(reinterpret_cast<std::complex<double>*>(data)); } /* Test */ int main() { // input array fftw_complex x[N]; // output array fftw_complex y[N]; // fill the first array with some numbers for (int idx = 0; idx < N; ++idx) { x[idx][REAL] = idx + 1; x[idx][IMAG] = 0; } // compute the FFT of x and store results in y fft(x, y); // display the results std::cout << "FFT =" << std::endl; displayComplex(y); // "shifted" results fftShift(y); std::cout << "\nFFT shifted =" << std::endl; displayComplex(y); return 0; }
内容的提问来源于stack exchange,提问作者OzzlyOsborne

