【问题标题】:C++ : How to programmatically define template types in runtime?C++:如何在运行时以编程方式定义模板类型?
【发布时间】:2018-11-01 21:10:16
【问题描述】:

我有一个要求,其中有一个枚举,并且为长度为 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()

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

【问题讨论】:

  • 你确定模板是正确的方法吗?这似乎是不必要的复杂
  • 是的,这是非常复杂的代码的一部分,在这里很难解释。我试图将其压缩到最低限度。
  • 语言是什么? C++?
  • 是的,它是 C++。
  • C++11、C++14 还是 C++17?另一个问题:我们可以想象向量长度的上限值吗?以 10 为例?

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


【解决方案1】:

所有这些模板函数都是在运行时定义和可用的(即编译器在编译时创建这些函数)

你确定这是个好主意吗? 因为,如果要选择运行时模板值,则必须实现、编译时所有可能的Alpha&lt;typeNumsValues...&gt;::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) &lt; 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});
 }

【讨论】:

    【解决方案2】:

    如果您想要运行时变量中的特定函数,请改用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);
    }
    

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

    【讨论】:

      【解决方案3】:

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

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

      编译器抛出错误——“错误:表达式必须有一个常量值”

      所以剩下的唯一选择是

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

      这是因为编译器需要知道所有可能的参数类型,使用哪些模板在编译期间会被调用。

      【讨论】:

      • 最终代码也不能工作......与原始代码不能工作的原因相同......如果值不是编译时常量并且使用模板没有多大意义,那么模板不是解决方案切换方法...
      • 是的,实际上。我正在删除最终代码。谢谢:)
      猜你喜欢
      • 2021-06-09
      • 1970-01-01
      • 1970-01-01
      • 2011-10-19
      • 2019-09-27
      • 2020-10-01
      • 2012-01-19
      • 1970-01-01
      • 2014-06-22
      相关资源
      最近更新 更多