3

I've been meddling with signal processing on my free time, and I'm writing my own DSP library as a learning exercise. I've written a function which computes the discrete fourier transform of a vector. There are two overloads of the function: one for const std::vector<float> &, and another for std::vector<std::complex<float>>.

I'd like to make this more generic. Instead of a vector, the function should accept a pair of generic random access iterators. The compiler should then deduce from the iterators whether it's complex or real valued data and select the right overload.


This should give you an idea,

//compute the discrete fourier transform in-place for a complex valued input
template<template<typename T> class Container, typename T, class RandomAccessIt>
void fft(
        typename Container<std::complex<T>>::RandomAccessIt first,
        typename Container<std::complex<T>>::RandomAccessIt last
    )
{
    ...
}

//calculate the discrete fourier transform for a real valued input
template<template<typename T> class Container1,
         template<std::complex<T>> class Container2,
         typename T,
         class RandomAccessIt1,
         class RandomAccessIt2>
void fft(
        typename Container1<T>::RandomAccessIt1 first,
        typename Container1<T>::RandomAccessIt1 last,
        typename Container2<std::complex<T>>::RandomAccessIt2 out_first
    )
{
    ...
    fft(...); //call the complex version
    ...        
}

but as you can see I don't really know what I'm doing with templates. How could I get that to work? If it isn't possible as-is, why?


EDIT:

I like the if constexpr -approach the most, as it solves the issue quite elegantly

template<typename T>
struct is_complex_t : public std::false_type {};

template<typename T>
struct is_complex_t<std::complex<T>> : public std::true_type {};

template<typename RandomAccessIt>
void fft(RandomAccessIt first, RandomAccessIt last)
{
    using Number_t = typename std::iterator_traits<RandomAccessIt>::value_type;
    if constexpr(!is_complex_t<Number_t>::value)
    {
        //math trickery to transform the real input data to complex-valued data
        ...
    }
    ... //the FFT itself
    if constexpr(!is_complex_t<Number_t>::value)
    {
        //math trickery to properly format the frequency domain data
        ...
    }
}  

That said, I have since realized that as the two overloads have a different number of parameters, I don't even need any clever metaprogramming:

//compute the discrete fourier transform in-place for a complex valued input
template<typename RandomAccessIt>
void fft(RandomAccessIt first, RandomAccessIt last)
{
    //...
}

//calculate the discrete fourier transform for a real valued input
template<typename RandomAccessIt1, typename RandomAccessIt2>
void fft(
        RandomAccessIt1 first, RandomAccessIt1 last,
        RandomAccessIt2 out_first
    )
{
    //...
    //fft(...); //call the complex version
    //...
}
Community
  • 1
  • 1
jms
  • 719
  • 2
  • 8
  • 18

1 Answers1

4

Well, just drop the Container and T template parameters, and take just the iterator type; this is what all of the standard library algorithms (e.g. in <algorithm>) do. Then use std::iterator_traits to get T as the iterator's value_type.

Now, for your specialization, you can use one of:

  • std::enable_if_t
  • tagged dispatch
  • constexpr-if - so you'll have the same function with two scopes for the two cases.

In this question:

if constexpr instead of tag dispatch

You'll see an example of how to use the second option (tagged dispatch) in the question, and an example of how to convert that to constexpr-if in one of the answers.

einpoklum
  • 118,144
  • 57
  • 340
  • 684