简体   繁体   中英

Calling templated function with type unknown until runtime

I have a this function to read 1d arrays from an unformatted fortran file:

template <typename T>
void Read1DArray(T* arr)
{
    unsigned pre, post;
    file.read((char*)&pre, PREPOST_DATA);

    for(unsigned n = 0; n < (pre/sizeof(T)); n++)
        file.read((char*)&arr[n], sizeof(T));

    file.read((char*)&post, PREPOST_DATA);
    if(pre!=post)
        std::cout << "Failed read fortran 1d array."<< std::endl;
}

I call this like so:

float* new_array = new float[sizeof_fortran_array];
Read1DArray(new_array);

Assume Read1DArray is part of a class, which contains an ifstream named 'file', and sizeof_fortran_array is already known. (And for those not quite so familiar with fortran unformatted writes, the 'pre' data indicates how long the array is in bytes, and the 'post' data is the same)

My issue is that I have a scenario where I may want to call this function with either a float* or a double*, but this will not be known until runtime.

Currently what I do is simply have a flag for which data type to read, and when reading the array I duplicate the code something like this, where datatype is a string set at runtime:

if(datatype=="float")
    Read1DArray(my_float_ptr);
else 
    Read1DArray(my_double_ptr);

Can someone suggest a method of rewriting this so that I dont have to duplicate the function call with the two types? These are the only two types it would be necessary to call it with, but I have to call it a fair few times and I would rather not have this duplication all over the place.

Thanks

EDIT: In response to the suggestion to wrap it in a call_any_of function, this wouldnt be enough because at times I do things like this:

if(datatype=="float")
{
    Read1DArray(my_float_ptr);
    Do_stuff(my_float_ptr);
}
else 
{
    Read1DArray(my_double_ptr);
    Do_stuff(my_double_ptr);
}

// More stuff happening in between  

if(datatype=="float")
{
    Read1DArray(my_float_ptr);
    Do_different_stuff(my_float_ptr);
}
else 
{
    Read1DArray(my_double_ptr);
    Do_different_stuff(my_double_ptr);
}

If you think about the title you will realize that there is a contradiction in that the template instantiation is performed at compile time but you want to dispatch based on information available only at runtime. At runtime you cannot instantiate a template, so that is impossible.

The approach you have taken is actually the right one: instantiate both options at compile time, and decide which one to use at runtime with the available information. That being said you might want to think your design.

I imagine that not only reading but also processing will be different based on that runtime value, so you might want to bind all the processing in a (possibly template) function for each one of the types and move the if further up the call hierarchy.


Another approach to avoid having to dispatch based on type to different instantiations of the template would be to loose some of the type safety and implement a single function that takes a void* to the allocated memory and a size argument with the size of the type in the array. Note that this will be more fragile, and it does not solve the overall problem of having to act on the different arrays after the data is read, so I would not suggest following this path.

Because you don't know which code path to take until runtime, you'll need to set up some kind of dynamic dispatch. Your current solution does this using an if-else which must be copied and pasted everywhere it is used.

An improvement would be to generate a function that performs the dispatch. One way to achieve this is by wrapping each code path in a member function template, and using an array of member function pointers that point to specialisations of that member function template. [Note: This is functionally equivalent to dynamic dispatch using virtual functions.]

class MyClass
{
public:

    template <typename T>
    T* AllocateAndRead1DArray(int sizeof_fortran_array)
    {
        T* ptr = new T[sizeof_fortran_array];
        Read1DArray(ptr);
        return ptr;
    }

    template <typename T>
    void Read1DArrayAndDoStuff(int sizeof_fortran_array)
    {
        Do_stuff(AllocateAndRead1DArray<T>(sizeof_fortran_array));
    }

    template <typename T>
    void Read1DArrayAndDoOtherStuff(int sizeof_fortran_array)
    {
        Do_different_stuff(AllocateAndRead1DArray<T>(sizeof_fortran_array));
    }

    // map a datatype to a member function that takes an integer parameter
    typedef std::pair<std::string, void(MyClass::*)(int)> Action;

    static const int DATATYPE_COUNT = 2;

    // find the action to perform for the given datatype
    void Dispatch(const Action* actions, const std::string& datatype, int size)
    {
        for(const Action* i = actions; i != actions + DATATYPE_COUNT; ++i)
        {
            if((*i).first == datatype)
            {
                // perform the action for the given size
                return (this->*(*i).second)(size);
            }
        }
    }
};

// map each datatype to an instantiation of Read1DArrayAndDoStuff
MyClass::Action ReadArrayAndDoStuffMap[MyClass::DATATYPE_COUNT] = {
    MyClass::Action("float", &MyClass::Read1DArrayAndDoStuff<float>),
    MyClass::Action("double", &MyClass::Read1DArrayAndDoStuff<double>),
};

// map each datatype to an instantiation of Read1DArrayAndDoOtherStuff
MyClass::Action ReadArrayAndDoOtherStuffMap[MyClass::DATATYPE_COUNT] = {
    MyClass::Action("float", &MyClass::Read1DArrayAndDoOtherStuff<float>),
    MyClass::Action("double", &MyClass::Read1DArrayAndDoOtherStuff<double>),
};


int main()
{
    MyClass object;
    // call MyClass::Read1DArrayAndDoStuff<float>(33)
    object.Dispatch(ReadArrayAndDoStuffMap, "float", 33);
    // call MyClass::Read1DArrayAndDoOtherStuff<double>(542)
    object.Dispatch(ReadArrayAndDoOtherStuffMap, "double", 542);
}

If performance is important, and the possible set of types is known at compile time, there are a few further optimisations that could be performed:

  • Change the string to an enumeration that represents all the possible data types and index the array of actions by that enumeration.

  • Give the Dispatch function template parameters that allow it to generate a switch statement to call the appropriate function.

For example, this can be inlined by the compiler to produce code that is (generally) more optimal than both the above example and the original if-else version in your question.

class MyClass
{
public:

    enum DataType
    {
        DATATYPE_FLOAT,
        DATATYPE_DOUBLE,
        DATATYPE_COUNT
    };

    static MyClass::DataType getDataType(const std::string& datatype)
    {
        if(datatype == "float")
        {
            return MyClass::DATATYPE_FLOAT;
        }
        return MyClass::DATATYPE_DOUBLE;
    }

    // find the action to perform for the given datatype
    template<typename Actions>
    void Dispatch(const std::string& datatype, int size)
    {
        switch(getDataType(datatype))
        {
        case DATATYPE_FLOAT: return Actions::FloatAction::apply(*this, size);
        case DATATYPE_DOUBLE: return Actions::DoubleAction::apply(*this, size);
        }
    }
};

template<void(MyClass::*member)(int)>
struct Action
{
    static void apply(MyClass& object, int size)
    {
        (object.*member)(size);
    }
};

struct ReadArrayAndDoStuff
{
    typedef Action<&MyClass::Read1DArrayAndDoStuff<float>> FloatAction;
    typedef Action<&MyClass::Read1DArrayAndDoStuff<double>> DoubleAction;
};

struct ReadArrayAndDoOtherStuff
{
    typedef Action<&MyClass::Read1DArrayAndDoOtherStuff<float>> FloatAction;
    typedef Action<&MyClass::Read1DArrayAndDoOtherStuff<double>> DoubleAction;
};


int main()
{
    MyClass object;
    // call MyClass::Read1DArrayAndDoStuff<float>(33)
    object.Dispatch<ReadArrayAndDoStuff>("float", 33);
    // call MyClass::Read1DArrayAndDoOtherStuff<double>(542)
    object.Dispatch<ReadArrayAndDoOtherStuff>("double", 542);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM