Учитывая параметр функции типа Container: Iterator, как достичь определенной перегрузки для определенных типов T?


В свободное время я занимаюсь обработкой сигналов и пишу собственную библиотеку DSP в качестве учебного упражнения. Я написал функцию, которая вычисляет дискретное преобразование Фурье вектора. Существует две перегрузки функции: одна для const std::vector<float> &, а другая для std::vector<std::complex<float>>.

Я хотел бы сделать это более общим. Вместо вектора функция должна принимать пару универсальных итераторов произвольного доступа. Компилятор должен затем вывести из итераторов, является ли это сложные или реально значимые данные и выбрать правильную перегрузку.


Это должно дать вам идею,

//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
    ...        
}
Но, как вы можете видеть, я действительно не знаю, что я делаю с шаблонами. Как мне заставить это работать? Если это невозможно как есть, то почему?

Правка:

Мне больше всего нравится if constexpr -подход, так как он решает проблему довольно элегантно

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
        ...
    }
}  
Тем не менее, с тех пор я понял, что поскольку две перегрузки имеют разное число параметров, я даже не нужно никакого умного метапрограммирования :
//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
    //...
}
1 3

1 ответ:

Ну, просто отбросьте параметры контейнера и шаблона T и возьмите только тип итератора; это то, что делают все стандартные библиотечные алгоритмы (например, в <algorithm>). Затем используйте std::iterator_traits чтобы получить T в качестве итератора value_type.

Теперь, для вашей специализации, вы можете использовать один из:

  • std::enable_if_t
  • помеченная отправка
  • constexpr-if-so у вас будет одна и та же функция с двумя областями для двух случаев.

В этом вопросе:

Если constexpr вместо тега dispatch

Вы увидите пример того, как использовать второй вариант (tagged dispatch) в вопросе, и пример того, как преобразовать его в constexpr-if в одном из ответов.