I am writing a data processor, and would like to be able to perform real-to-real, and real-to-complex computations. The setup I have right now:
// class to hold various data types
template <typename T>
class DataArray
{
public:
DataArray(){};
T *ptr;
};
// Processing configuration
template <typename T>
class Config
{
public:
Config(){};
T var;
};
// The data processor itself, takes input, output array and the processing configuration
template <typename Tin, typename Tout>
class Process
{
public:
DataArray<Tin> *in;
DataArray<Tout> *out;
Config<Tout> *config;
Process(){};
Process(DataArray<Tin> *in_, DataArray<Tout> *out_, Config<Tout> *config_)
{
in = in_;
out = out_;
config = config_;
}
};
Currently, I can handle real-to-real processing as follows:
int main()
{
DataArray<int16_t> input;
DataArray<float> output;
Config<float> config;
// real 2 real processing
Process<int16_t, float> P1(&input, &output, &config); // works fine
return 0;
}
The problem I am facing is that when I want to do real-to-complex processing, I still need my Config
to be in the underlying type of std::complex<T>
. How can I change the Process
class (or any other class) such that I can create a class Process<int16_t, std::complex<float>>
that will know that it expects a Config<float>
?
int main()
{
DataArray<int16_t> input;
DataArray<std::complex<float>> output_complex;
Config<float> config;
// real 2 complex processing
Process<int16_t, std::complex<float>> P2(&input, &output, &config); // Obviously does not work
return 0;
}
Follow up question
Thanks to the Remy's answer, I can now get the type of std::complex in the class Process
. If I have a namespace with templated functions, how would I obtain the underlying type in that function/namespace? The following works, but feels a bit clunky. This means that I have to repeat the line using T = typename helper::value_type_of<Tout>::type;
in every kernel, am I right?
// namespace with computation kernels
namespace kernels
{
// define ConfigType in the kernel namespace?
template <typename T>
using ConfigType = Config<typename helper::value_type_of<T>::type>;
template <typename Tin, typename Tout, bool IQ>
void bfpw(ConfigType<Tout> *BFC, Tin *RF, Tout *BF)
{
// get type of Tout: std::complex<float> or float
using T = typename helper::value_type_of<Tout>::type;
T variable = BFC->var;
}
}
template <typename Tin, typename Tout>
Process<Tin, Tout>::Process(DataArray<Tin> *in_, DataArray<Tout> *out_, ConfigType *config_)
{
in = in_;
out = out_;
config = config_;
kernels::bfpw<Tin, Tout, true>(config, in->ptr, out->ptr); // call the kernel
}
CodePudding user response:
One way is to define a helper template which Process
can use to detect whether Tout
is a std::complex
or not, and if so then it can use Tout
's value_type
member, otherwise it can use Tout
as-is.
For example:
namespace helper {
template<typename T>
struct value_type_of {
using type = T;
};
template<typename T>
struct value_type_of<std::complex<T>> {
using type = typename std::complex<T>::value_type;
// or simply: using type = T;
};
}
template <typename Tin, typename Tout>
class Process
{
public:
using ConfigType = Config<typename helper::value_type_of<Tout>::type>;
...
ConfigType *config;
Process(){};
Process(..., ConfigType *config_)
{
...
config = config_;
}
};
This way, Process<..., float>
and Process<..., std::complex<float>>
will both accept Config<float>
.