C ++:如何在运行时以编程方式定义模板类型?

时间:2018-11-01 21:10:17

标签: c++ c++11 templates variadic-templates template-meta-programming

我要有一个枚举,并且为所有可能的枚举组合定义模板函数,直到长度l。

说枚举是

Enum typenum {A, B, C}

所有这些模板函数都已定义并在运行时可用(即,编译器在编译时创建了这些函数)

Alpha<A>::f()
Alpha<B>::f()
Alpha<C>::f()
Alpha<A,A>::f()
Alpha<A,B>::f()
Alpha<A,C>::f()
Alpha<B,A>::f()
Alpha<B,B>::f()
Alpha<B,C>::f()
Alpha<C,A>::f()
Alpha<C,B>::f()
Alpha<C,C>::f()
and combination of 3 enums, 4 enums...

现在我必须根据输入向量选择合适的功能

void f(vector<enum> eVec){
    Alpha::f<eVec[0], eVec[1],... eVec[eVec.size() - 1]>() // <-------

我该怎么做?一种方法是为每种尺寸定义。例如:

if(eVec.size() == 1)
   Alpha<eVec[0]>::f()
else if(eVec.size() == 2)
   Alpha<eVec[0], eVec[1]>::f()

这不会扩展。是否有任何优雅,可扩展的方式来做到这一点。

3 个答案:

答案 0 :(得分:3)

  

所有这些模板函数都已定义并在运行时可用(即,编译器在编译时创建了这些函数)

您确定这是个好主意吗? 因为,如果要选择运行时模板值,则必须实现编译时所有可能的Alpha<typeNumsValues...>::f()组合。 如果不对可变参数列表强加一个长度限制,那是不可能的,但是当限制相对较低时,这在计算上也非常昂贵。

无论如何...假设您有一个如下的枚举

enum typeEnum { A, B, C };

和可变参数模板Alpha类,具有typeEnum模板值和如下的static方法f()

template <typeEnum ...>
struct Alpha
 { static void f () { /* do something */ } };

您的f()可以呼叫可变参数f_helper()

void f (std::vector<typeEnum> const & eVec)
 { f_helper<>(eVec, 0u); }

实现如下

template <typeEnum ...>
void f_helper (std::vector<typeEnum> const &, ...)
 { }

template <typeEnum ... Tes>
typename std::enable_if<(sizeof...(Tes) < 6u)>::type
    f_helper (std::vector<typeEnum> const & eVec, std::size_t index)
 {
   if ( index < eVec.size() )
      switch ( eVec[index++] )
       {
         case A: f_helper<Tes..., A>(eVec, index); break;
         case B: f_helper<Tes..., B>(eVec, index); break;
         case C: f_helper<Tes..., C>(eVec, index); break;
       }
   else
      Alpha<Tes...>::f();
 }

观察到我对可变参数列表的长度设置了非常低的限制(5,sizeof...(Tes) < 6u),因为已开发的Alpha的数量呈指数增长。

还要注意,我已经添加了f_helper()的禁止版本;这是必要的,因为小于6长度的递归调用f_helper()可以使用必须以某种方式管理的6个枚举的可变列表来调用它。

以下是完整的编译示例

#include <vector>    

enum typeEnum { A, B, C };

template <typeEnum ...>
struct Alpha
 { static void f () { } };

template <typeEnum ...>
void f_helper (std::vector<typeEnum> const &, ...)
 { }

template <typeEnum ... Tes>
typename std::enable_if<(sizeof...(Tes) < 6u)>::type
    f_helper (std::vector<typeEnum> const & eVec, std::size_t index)
 {
   if ( index < eVec.size() )
      switch ( eVec[index++] )
       {
         case A: f_helper<Tes..., A>(eVec, index); break;
         case B: f_helper<Tes..., B>(eVec, index); break;
         case C: f_helper<Tes..., C>(eVec, index); break;
       }
   else
      Alpha<Tes...>::f();
 }

void f (std::vector<typeEnum> const & eVec)
 { f_helper<>(eVec, 0u); }   

int main ()
 {
   f({A, B, C, A});
 }

答案 1 :(得分:1)

如果要从运行时变量中获取特定功能,请改用map。模板是工作的错误工具,因为您必须写很多代码才能将变量转换为常量。

假设您enum的默认值为None,并且您最多可以说5个参数,则可以这样定义一个映射:

enum MyEnum { None = 0, A, B, C, D... };
using MyKey = std::tuple<MyEnum, MyEnum, MyEnum, MyEnum, MyEnum>;
using MyFunction = std::function<void()>;

然后您在某处有功能图(单例)

std::map<MyKey, MyFunction> myMap;

实用程序功能可能有助于从可变数量的参数创建密钥:

MyKey MakeKey(MyEnum e1, MyEnum e2 = None, MyEnum e3 = None, MyEnum e4 = None, MyEnum e5 = None)
{
    return std::make_tuple(e1, e2, e3, e4, e5);
}

myMap.emplace(MakeKey(A, B), [](){ /* some code */ });

MyEnum AtOrDefault(const vector<enum> &eVec, int index)
{
    return index < eVec.size() ? eVec[index] : None;
}

然后假设您要从向量中调用适当的函数,则可以执行以下操作:

void f(const vector<enum> &eVec)
{
    if (eVec.size() > 5) throw some_exception;

    MyKey key = std::make_typle(
        AtOrDefault(eVec, 0), 
        AtOrDefault(eVec, 1), 
        AtOrDefault(eVec, 2), 
        AtOrDefault(eVec, 3), 
        AtOrDefault(eVec, 4));

    auto &fn = myMap[key];
    fn();
}

您还可以使用假设您知道枚举中元素的最大数量来计算值的想法。然后,您可以创建一个CombinedEnumType:

enum CombinedEnumType : uint32_t { };

并定义了一个函数

CombinedEnumType MakeCombinedEnumType(MyEnum e1, … MyEnum e5 = None) 
{
    const int MyEnumEcount = 5;
    MyEnum a[] = { e1, e2, e3, e4, e5 };

    uint32_t result = 0;
    for (auto & item : a) { result *= MyEnumEcount; result += item; }
    return static_cast<CombinedEnumType>(result);
}

此代码仅供参考。在实际的生产代码中,您必须使用常量,正确的变量名,验证给定组合是否存在函数……

答案 2 :(得分:0)

回答我自己的问题。我已经意识到这种方法行不通。因为当我们写类似

Alpha<eVec[0]>::f()

编译器抛出错误-“错误:表达式必须具有常量值”

所以剩下的唯一选择是

if(eVec.size() == 1){
    switch(eVec[0]){
        case A:
            Alpha<A>::f()
.....

这是因为编译器需要了解所有可能的参数类型,并在编译期间使用哪种模板进行调用。