在运行时调用带有未知类型的模板化函数

时间:2013-08-16 13:30:08

标签: c++ templates

我有一个函数从未格式化的fortran文件中读取1d数组:

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;
}

我这样称呼:

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

假设Read1DArray是一个类的一部分,它包含一个名为'file'的ifstream,并且sizeof_fortran_array已经知道。 (对于那些不太熟悉fortran无格式写入的人来说,'pre'数据表示数组以字节为单位的时间长度,'post'数据是相同的)

我的问题是我有一个场景,我可能想用float *或double *来调用这个函数,但是直到运行时才会知道。

目前我所做的只是有一个标志,用于读取数据类型,在读取数组时,我复制了类似这样的代码,其中datatype是在运行时设置的字符串:

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

有人可以建议一种重写方法,以便我不必复制两种类型的函数调用吗?这是用来调用它的唯一两种类型,但是我必须把它称为公平几次,我宁愿不要在整个地方都有这种重复。

由于

编辑: 为了回应将它包装在call_any_of函数中的建议,这是不够的,因为有时候我会做这样的事情:

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);
}

2 个答案:

答案 0 :(得分:3)

如果您考虑标题,您会发现存在一个矛盾,即模板实例化是在编译时执行的,但您希望根据仅在运行时可用的信息进行调度。在运行时,您无法实例化模板,因此这是不可能的。

您采用的方法实际上是正确的方法:在编译时实例化两个选项,并决定在运行时使用哪个选项和可用信息。话虽如此,你可能想要考虑你的设计。

我想不仅基于该运行时值,读取和处理也会有所不同,因此您可能希望将每个类型的(可能是模板)函数中的所有处理绑定在一起并移动{{1}进一步调用层次结构。


另一种避免必须根据类型分配到模板的不同实例的方法是松开一些类型的安全性并实现一个单独的函数,它将if带到已分配的内存和{{1带有数组中类型大小的参数。请注意,这将更加脆弱,并且它无法解决在读取数据后必须对不同阵列执行操作的整体问题,因此我不建议遵循此路径。

答案 1 :(得分:1)

因为您不知道在运行时要使用哪个代码路径,所以您需要设置某种动态分派。您当前的解决方案使用if-else执行此操作,必须在使用它的任何位置复制和粘贴它。

改进是生成执行调度的函数。实现此目的的一种方法是将每个代码路径包装在成员函数模板中,并使用指向该成员函数模板的特化的成员函数指针数组。 [注意:这在功能上等同于使用虚函数的动态调度。]

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);
}

如果性能很重要,并且在编译时可以知道可能的类型集,那么可以执行一些进一步的优化:

  • 将字符串更改为表示所有可能数据类型的枚举,并通过该枚举索引操作数组。

  • 提供Dispatch函数模板参数,以允许它生成一个switch语句来调用相应的函数。

例如,编译器可以内联这个代码,以生成(通常)比上述示例和问题中的原始if-else版本更优化的代码。

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);
}