【问题标题】:Associating enums with template classes at compile time在编译时将枚举与模板类关联
【发布时间】:2017-03-17 09:13:40
【问题描述】:

我有一个模板类如下:

template<typename A> struct TaskInfo{
    typedef A       _A;
    static void bar(A a){blah blah...} };

template <typename TaskInfo> class Task   {
    typedef typename TaskInfo::_A A;
    bar(A a){
        blah blah...
        TaskInfo::bar(a);
    }   
}

我有一个包含这些类的对象:

using TaskInfoX= TaskInfo<int>; //ignore the bar implementation for the time being.
using TaskInfoY= TaskInfo<double>;

class TaskCollection(){ 
    TaskCollection(){
        auto Task1=new Task<TaskInfoX>;
        auto Task2=new Task<TaskInfoY>;
        Register(Task1);
        Register(Task2);
    }
Register(...);
}

我想知道是否可以定义一个枚举列表:

enum TaskEnum
{
    Etask1,
    Etask2
};

还有一个函数getTask,这样我就可以在我的应用中拥有:

int main {
    TaskCollection collection;
    int testInt;
    double testDouble;
    collection.getTask(Etask1)->bar(testInt);
    //collection.getTask(Etask1)->bar(testDouble);  //want compile error.
    collection.getTask(Etask2)->bar(testDouble);
}

我知道我可以拥有 CRTP 或虚拟继承等价物,它允许我为 bar() 传递可变参数,但我想在编译时对 bar 函数的参数进行类型检查。这在 C++ 中是不可能的吗?

更新:为错字道歉。本来应该是:getTask(task1)。基本上外界不知道任务的底层结构,只知道它们的公共枚举密钥。另请注意,通常会有其他任务可能重用 typeInfoX 参数。

【问题讨论】:

  • CRTP 都是编译时间所以你为什么要费心使用它?
  • collection.getTask&lt;Task1&gt;()-&gt;bar(testInt); 之类的东西可能是可能的。
  • 为错字道歉。本来是这样的:getTask(task1) 基本上外界不知道任务的底层结构,只知道它们的公共枚举密钥。
  • @πάνταῥεῖ 不确定我是否理解您的问题,但正如我所说,我想要对 bar 参数进行静态类型检查
  • @shu 基于 CRTP 的模式和调度的重点是在编译时完成的静态类型检查(也称为静态多态性)。

标签: c++ templates types type-conversion


【解决方案1】:

首先,如果类型不完全匹配,则出现错误,您可以使用以下内容:

template <typename T>
class Task
{
public:
    using type = typename T::type;

    void bar(type a) { T::bar(a); }

    template <typename U>
    std::enable_if_t<!std::is_same<std::decay_t<U>, type>::value>
    bar(U&&) = delete;
};

然后,有一些助手:

template <TaskEnum> struct TaskMap;

template <> struct TaskMap<Etask1> { using type = Task<TaskInfoX>; };
template <> struct TaskMap<Etask2> { using type = Task<TaskInfoY>; };

您的收藏可能类似于:

class TaskCollection
{ 
public:
    Task<TaskInfoX> taskX;
    Task<TaskInfoY> taskY;

    template <TaskEnum E>
    typename TaskMap<E>::type&
    getTask();
};

template <>
Task<TaskInfoX>& TaskCollection::getTask<Etask1>() { return taskX; }

template <>
Task<TaskInfoY>& TaskCollection::getTask<Etask2>() { return taskY; }

最终用法:

collection.getTask<Etask1>().bar(testInt);
collection.getTask<Etask1>().bar(testDouble);//error:call to deleted member function 'bar'
collection.getTask<Etask2>().bar(testDouble);

Demo

【讨论】:

    【解决方案2】:

    你总是可以用原语模板化一个具体的类。所以而不是:

    using TaskInfoX= TaskInfo<int>;
    using TaskInfoY= TaskInfo<double>;
    

    你可以拥有

    template<> class Task<TaskEnum::Task1> : public TaskInfo<int>{}
    template<> class Task<TaskEnum::Task2> : public TaskInfo<double>{}
    

    然后定义一个模板函数:

    template<TaskEnum taskE>    
    Task<taskE>* getTask() {}
    

    你需要从一个基类派生模板类Task,这样你就可以把它放在一个地图中,你应该定义一个地图

    std::map<TaskEnum,TaskBase*>    taskMap;
    

    在 getTask 中你可以做一个静态转换:

    static_cast<Task<taskE>* >(taskBasePtr);
    

    【讨论】:

      【解决方案3】:

      我认为std::tuple 是您所需要的:

      auto my_tuple = std::make_tuple(Task<TaskInfoX>(), Task<TaskInfoY>());
      std::get<Task<TaskInfoX>>(my_tuple).bar(12);
      // actually, this is not an error because double can be convert to int
      std::get<Task<TaskInfoX>>(my_tuple).bar(12.323);
      

      【讨论】:

      • 为错字道歉。本来是这样的:getTask(task1) 基本上外界不知道任务的底层结构,只知道它们的公共枚举密钥。
      • 不知道您可以在元组中进行类型访问。凉爽的。我想我可以有类似的方法并通过索引访问元组,但这意味着我需要维护我的任务列表以及不方便的元组。
      【解决方案4】:

      鉴于 Task1 和 Task2 存储在您的 TaskCollection 类中的方式,我看不到将 getTask 实现为模板的明显方法,但您可以重载它。

      class TaskCollection
      {
          //...
          Task<TaskInfoX> GetTask(Task<TaskInfoX> task)
          {
              return Task1;
          }
      
          Task<TaskInfoY> GetTask(Task<TaskInfoY> task)
          {
              return Task2;
          }
      };
      

      如果您的实际情况适用于 double 和 int,那么您需要抑制从 double 到 int 的转换,以实现此编译时错误...

      //collection.getTask(Task1)->bar(testDouble); //想要编译错误。

      一种方法是在 TaskInfo 中声明但不定义 bar 的模板版本

      template<typename A> struct TaskInfo
      {   
          static void bar(A a){blah blah...}
          template<typename T> static void bar(T a); // deliberately not defined
          //...
      };
      

      你仍然为你想要处理的类型定义你现有的 bar 函数,但是现在,如果你想失败,编译器将尝试调用 bar 的模板版本(以 T 作为双精度)更喜欢通过从 double 到 int 的转换来调用 int 版本。 VS2015中产生的错误是

      错误 LNK2019: 无法解析的外部符号“public: static void __thiscall C::bar(double)

      但如果没有代码尝试调用它,那么它没有定义也没关系,没有错误。

      (我不清楚关于枚举的问题是什么)

      【讨论】:

      • 为错字道歉。这意味着:getTask(task1) 而不是 Task1。基本上外界不知道任务的底层结构,只知道它们的公共枚举密钥。至于我的真实示例,任务基本上代表委托/信号对象,我将定义正确类型的函数指针等类型。所以不,不是原语。
      • 在这种情况下,我会在枚举值上使用模板专门化,但 Jarod42 添加了一个足够好的答案。
      猜你喜欢
      • 2016-02-07
      • 2016-08-02
      • 2016-04-24
      • 1970-01-01
      • 1970-01-01
      • 2016-06-09
      • 2012-09-25
      • 1970-01-01
      • 2020-09-27
      相关资源
      最近更新 更多