Home > Mobile >  Templated template function as input parameter to function in C
Templated template function as input parameter to function in C

Time:11-13

I have several classes which all receive raw data on the form of an uint8 buffer, and the underlying datatype of the data in the buffer is decided at runtime. The buffer needs to go through a converter function that's templated, and decided in a big switch statement at runtime, depending on the received data type which templated version of the converter function that's going to be executed. So I have one 'inner' converter function, and one 'outer' switch function that calls different templated versions of the 'inner' function, depending on the data type that the underlying data had. Since the 'inner' converter functions all look a bit different, I have until now had a separate 'outer' function for every 'inner' function.

Now I want to do a more general 'outer' function that can take the the 'inner' function as an input argument. But I'm a bit stuck on how to do the templating.

This is my current approach:

struct InputParams
{
    size_t num_elements;
    size_t num_bytes_per_vec;
};

struct OutputData
{
    float *q0;
    float *q1;
};

template <typename T> OutputData testFunction(const uint8_t* const input_data, const InputParams& input_params)
{
    OutputData output;
    // Allocate 'output' in some way...
    const T* const t_ptr = reinterpret_cast<const T* const>(input_data);
    
    for(size_t k = 0; k < input_params.num_elements; k  )
    {
        output.q0[k] = input_data[k];
        output.q1[k] = ...
        // ...
    }

    return output;

}

template <typename O, template <typename> typename F, typename T, typename I>
O applyFunctionForDataType(const uint8_t* const input_data,
                           const DataType data_type,
                           const F<T>& converter_function,
                           const I& input_params)
{
    O output_data;

    if (data_type == DataType::FLOAT)
    {
        output_data = converter_function<float>(input_data, input_params);
    }
    else if (data_type == DataType::DOUBLE)
    {
        output_data = converter_function<double>(input_data, input_params);
    }
    else if (data_type == DataType::INT8)
    {
        output_data = converter_function<int8_t>(input_data, input_params);
    }
    else if ...

    return output_data;
}

The template arguments typename O and typename I are for the output data structure, and input parameters, as these differ depending on which function that will call all of this. In one use case, OutputData might have two float pointers, in another a float pointer and an int pointer. InputParams will probably always be pretty similar, but I figured I might as well have that as a template arguments, since OutputData already is one.

Calling applyFunctionForDataType:

InputParams input_params;
OutputData oo = applyFunctionForDataType<OutputData>(data_ptr_, data_type_, testFunction, input_params);

I get the following error message from the compiler:

<source>:68:23: error: 'converter_function' does not name a template but is followed by template arguments
        output_data = converter_function<float>(input_data, input_params);
                      ^                 ~~~~~~~
<source>:61:40: note: non-template declaration found by name lookup
                           const F<T>& converter_function,

Compiler test

Alternative solutions are of course welcome.

CodePudding user response:

Basically templates doesn't work that way. Templates always resolved at compile-time and different template function specalizations has different addresses, so you can't pass them through one function argument. You need to somehow pass all possible variants. Either though arguments:

template <typename O, typename I>
O applyFunctionForDataType(const uint8_t* const input_data,
                           const DataType data_type,
                           O(*converter_function_float)(const uint8_t* const, const InputParams&),
                           O(*converter_function_double)(const uint8_t* const, const InputParams&),
                           const I& input_params)
{
...
}

// Calling
InputParams input_params;
OutputData oo = applyFunctionForDataType<OutputData>(data_ptr_, data_type_, testFunction<float>, testFunction<double>, input_params);

or you can use std::array or std::vector or std::map to pass these functions in one parameter and then select needed one based on data_type variable.

template <typename O>
using converter_function_type = O(*)(const uint8_t* const, const InputParams&);

template <typename O, typename I>
O applyFunctionForDataType(const uint8_t* const input_data,
                           const DataType data_type,
                           std::map<DataType, converter_function_type<O>> converters,
                           const I& input_params)
{
    return converters[data_type](input_data, input_params);
}

std::map<DataType, converter_function_type<OutputData>> cnv_funcs = {
    {DataType::FLOAT, testFunction<float>},
    {DataType::DOUBLE, testFunction<double>},
};

int main()
{
    InputParams input_params;
    const uint8_t* const input_data = new uint8_t[20];
    DataType dt = DataType::DOUBLE;
    OutputData oo = applyFunctionForDataType<OutputData>(input_data, dt, cnv_funcs, input_params);

    return 0;
}

CodePudding user response:

You cannot pass function templates to your template. You could however pass an object providing a template function:


template <typename O, typename Converter, typename I>
O applyFunctionForDataType(const uint8_t* const input_data,
    const DataType data_type,
    Converter&& converter,
    const I& input_params)
{
    O output_data;

    if (data_type == DataType::FLOAT)
    {
        output_data = std::forward<Converter>(converter).template Convert<float>(input_data, input_params);
    }
    else if (data_type == DataType::DOUBLE)
    {
        output_data = std::forward<Converter>(converter).template Convert<double>(input_data, input_params);
    }
    else if (data_type == DataType::INT8)
    {
        output_data = std::forward<Converter>(converter).template Convert<int8_t>(input_data, input_params);
    }

    return output_data;
}


struct TestFunctionConverter
{
    template<class T>
    OutputData Convert(const uint8_t* const inputData, const InputParams& inputParams) const
    {
        return testFunction<T>(inputData, inputParams);
    }
};

int main()
{
    ...
    OutputData oo = applyFunctionForDataType<OutputData>(input_data, dt, TestFunctionConverter{}, input_params);
}

Alternatively you could pass a type as template template parameter, that provides a function for conversion:

template <typename O, template <typename> class Converter, typename I>
O applyFunctionForDataType(const uint8_t* const input_data,
    const DataType data_type,
    const I& input_params)
{
    O output_data;

    if (data_type == DataType::FLOAT)
    {
        output_data = Converter<float>{}(input_data, input_params);
    }
    else if (data_type == DataType::DOUBLE)
    {
        output_data = Converter<double>{}(input_data, input_params);
    }
    else if (data_type == DataType::INT8)
    {
        output_data = Converter<int8_t>{}(input_data, input_params);
    }

    return output_data;
}

template<class T>
struct TestFunctionConverter
{
    OutputData operator()(const uint8_t* const inputData, const InputParams& inputParams) const
    {
        return testFunction<T>(inputData, inputParams);
    }
};

int main()
{
    ...
    OutputData oo = applyFunctionForDataType<OutputData, TestFunctionConverter>(input_data, dt, input_params);
    ...
}
  • Related