C++:尝试使用fftw_complex数据使用std::rotate会导致错误:“数组必须用花括号包含的初始化器进行初始化”。

4

我正在尝试理解如何利用FFT处理我用软件定义收音机捕获的数据。我发现了C++的fftw3库,并尝试查找一些文档或教程,但发现没有太多可供参考的内容。

然而我找到了这个tutorial。用于执行fft/ifft等操作的代码对我来说编译和工作得很好,但当我尝试实现fftShift函数时,我遇到了以下编译器错误:

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

这是我用来编译代码的命令行:g++ CppFFTW.cpp -lfftw3 -lm
g++ --version 的输出结果为: 'g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0'

这个错误信息对我来说没有太多意义,但我猜测它的意思是算法的 std::rotate 函数不再与 fftw_complex 数据类型兼容。

这让我有了几个问题:

  1. 为什么这种方法在教程视频中有效,但对我无效?
  2. 有哪些方法可以理解这个错误信息的含义?
  3. 如何实现一个可工作的 fftShift 函数?
  4. 是否应该使用不同的编译器版本?

fftShift 的作用是将零分量移动到中心位置,就像这个例子一样。

When # elements odd:
{a, b, c, d, e, f, g} -> {e, f, g, a, b, c, d} 

When # elements even:
{a, b, c, d, e, f, g, h} -> {e, f, g, h, a, b, c, d}

这里是fftShift的定义: 编辑:原始(损坏的)fftShift
// 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]);
    }
}

这里是代码的剩余部分(与问题无关的函数/调用被省略):编辑:根据Ted的贡献添加#include并修复fftShift函数。
* 
 Example code for how to utilize the FFTW libs
 Adapted from damian-dz C++ Tutorial
 https://github.com/damian-dz/CppFFTW/tree/master/CppFFTW
 (https://www.youtube.com/watch?v=geYbCA137PU&t=0s)
 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> // Added post feedback from Ted

// macros for real & imaginary parts
#define REAL 0
#define IMAG 1

// length of the complex arrays
#define N 8

/* Computres 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;
        }
    }
}

// Edit: Adjusted fftShift function per Ted's feedback
void fftShift(std::complex<double>* data) {
    static_assert(sizeof(fftw_complex) == sizeof(std::complex<double>));

    // 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]);
    }
}
// Edit: Accompanying fftShift to re-cast data
void fftShift(fftw_complex* data) {
    fftShift(reinterpret_cast<std::complex<double>*>(data));
}

// Original (broken) 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]);
    }
}

/* 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;
}

我没有看到任何明显的现有问题适用于我的情况(可能是我错了,因为我还在学习C++),所以我创建了一个帐户,并且这是我在这里的第一个问题。请随意提供反馈,以便我可以使我的问题更容易理解。


如果您仔细观看视频,作者会迅速更改函数为 void fftShift(double *data)。它有点偶然地发生了作用。 - alfC
@alfC 你说得对 - 我之前没注意到。不过我想知道为什么?实际上,即使不改变它,它也可以在VS2022中编译通过。 - Ted Lyngmo
@TedLyngmo,也许VS2022不会报告未使用的函数错误?这是你想知道的吗? - alfC
@alfC 不,我想知道为什么在MSVC中使用fftw_complex*能正常工作,为什么要改成double *。编辑:啊,等等...他们还保留了另一个重载...好的,我明白了。double*重载没有被使用。 :-) - Ted Lyngmo
@OzzlyOsborne,没问题,请使用这个链接,上面的链接已经失效了:https://gitlab.com/correaa/boost-multi/-/blob/master/include/multi/adaptors/fftw/test/so_shift.cpp - alfC
显示剩余4条评论
1个回答

5

std::complex

对于任何类型为 std::complex<T> 的对象 zreinterpret_cast<T(&)[2]>(z)[0]z 的实部,reinterpret_cast<T(&)[2]>(z)[1]z 的虚部。

这个要求的目的是保持 C++ 库复数类型和 C 语言复数类型(及其数组)之间的二进制兼容性,它们具有相同的对象表示要求。

现在,fftw_complex 不一定是在 C 语言中定义的复数类型 - 但你可能可以通过进行类似的转换来解决问题。事实上,在 fftw3.h 头文件中的注释表明这很可能是可行的:

/* If <complex.h> is included, use the C99 complex type.  Otherwise
   define a type bit-compatible with C99 complex */
#if !defined(FFTW_NO_Complex) && defined(_Complex_I) && defined(complex) && defined(I)
#  define FFTW_DEFINE_COMPLEX(R, C) typedef R _Complex C
#else
#  define FFTW_DEFINE_COMPLEX(R, C) typedef R C[2]
#endif

C++中fftw_complex的定义变成了double[2],这也是为什么std::rotate失败的原因 - 你不能将数组分配给数组。

所以我会像这样定义我的函数,使用fftw_complex*

void fftShift(std::complex<double>* data) {
    static_assert(sizeof(fftw_complex) == sizeof(std::complex<double>));

    // 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]);
    }
}

void fftShift(fftw_complex* data) {
    fftShift(reinterpret_cast<std::complex<double>*>(data));
}

int main() {
    fftw_complex data[N];
    fftShift(data);
}

一个更好的选择是在你的C++代码中使用std::complex<double>,并且只有在调用fftw函数时才转换为fftw_complex*
std::vector<std::complex<double>> wave;

fftw_function( reinterpret_cast<fftw_complex*>(wave.data()), wave.size() );

  1. 为什么这个方法在教程视频中可以运行,但我却不行?

这是因为教程中使用的是Visual Studio,而该版本的标准库中的std::rotate实现使用了std::iter_swap,它实际上能够交换整个数组。如果iter_swap被标准要求能够做到这一点,我不知道,但是g++、clang++、icx和MSVC实现中的iter_swap都能够做到这一点。然而,在这种情况下,g++、clang++和icx并没有在它们的rotate实现中使用自己的iter_swap

我们可以从rotate @ cppreference借用一个实际使用std::iter_swaprotate实现,然后所有四个都可以使用自己的iter_swaprotate您的fftw_complexs。演示

  1. 有哪些方法可以理解这个错误信息的含义?

这意味着std::rotate试图将一个double[2]分配给另一个double[2],就好像通过执行以下操作一样:as-if

double a[2]{};
double b[2]{};
a = b;          // error: invalid array assignment

3. 如何实现一个可用的fftShift函数?
参见上文。
4. 我应该使用不同的编译器版本吗?
不需要,即使是更新的g++clang++版本也会出现相同的问题。您可以使用Visual Studio或不同的rotate实现(例如cppreference.com上的实现),但我建议只需使用std::complex<double>并在需要时将其转换为fftw_complex

感谢反馈!您建议的代码对我有用(在我的代码块顶部添加了#include <complex>)。理解更复杂的数据类型及其如何使用仍然对我来说有些困惑。 - OzzlyOsborne
@OzzlyOsborne 不用客气!我不得不在Visual Studio库中深入挖掘,才找出教程为什么实际上能够正常工作。解释已添加到答案中。 - Ted Lyngmo
我感到困惑。即使使用iter_swap或其他方法可以直接交换“数组”,但在这里它并没有帮助,会导致错误的结果,因为实际数据应该被交换(每次交换两个双精度元素)。没有实际有用的指针可以交换。 - alfC
1
@alfC 是的,这也是 fftw 代码块下面的注释所指的。g++ 和 clang++ 版本尝试将一个数组分配给另一个数组,但失败了 - 但由于两个实现都有一个 iter_swap,可以实际交换两个数组中的值,似乎还有其他更多的事情发生了。现在我无法检查,但我怀疑那些 std::rotate 的实现最终 没有 使用 std::iter_swap,这会使它起作用。 - Ted Lyngmo
在我看来,除非在调用fftw函数时使用reinterpret cast,否则根本不应该使用fftw_complex。@TedLyngmo - alfC
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接