【问题标题】:Enum variable as a dynamic template argument枚举变量作为动态模板参数
【发布时间】:2012-04-16 14:08:32
【问题描述】:

假设我们有一个枚举类型:

enum DataType { INT, DOUBLE };

还有一个类型映射器:

template<DataType T>
struct TypeTraits {}; 

template<>
struct TypeTraits<INT> { typedef int T; };

template<>
struct TypeTraits<DOUBLE> { typedef double T; };

还有一些代表操作的模板(不要担心丑陋的 void 指针和类似 C 的类型转换):

struct Operation {
  DataType rettype;

  Operation(DataType rettype) : rettype(rettype);
  virtual void* compute();
};

template<DataType RetType>
class Constant : public Operation {
  typedef typename TypeTraits<RetType>::T RType;
  RType val;

  Constant(RType val) : val(val), Operation(RetType) {}; 
  virtual void* compute(){ return &val; }
};  

template<DataType T1, DataType T2, DataType RetType>
class Add : public Operation {
  typedef typename TypeTraits<RetType>::T1 T1Type;
  typedef typename TypeTraits<RetType>::T2 T2Type;
  typedef typename TypeTraits<RetType>::RetType RType;
  RType val;

  Operation *c1, *c2;

  Add(Operation *c1, Operation *c2) : c1(c1), c2(c2), Operation(RetType) {}; 

  virtual void* compute(){
    T1Type *a = (T1Type *)c1->compute();
    T2Type *b = (T2Type *)c2->compute();
    val = *a + *b; 
    return &val;
  }   
};  

还有一个抽象的树表示:

class AbstractNode {
  enum Type { ADD, INT_CONSTANT, DOUBLE_CONSTANT };

  Type type;
  int intval;
  double doubleval;
  child1 *AbstractNode;
  child2 *AbstractNode;
}

我们正在从输入中读取序列化的抽象树,以便将其转换为操作树,然后 - 计算结果。

我们想写这样的东西:

algebrator(Operation *op){
  if(op->type == AbstractNode::INT_CONSTANT)
    return new Constant<INT>(op->intval);
  else if(op->type == AbstractNode::DOUBLE_CONSTANT)
    return new Constant<DOUBLE>(op->doubleval);
  else {
    Operation *c1 = algebrator(op->child1),
              *c2 = algebrator(op->child2);
    DataType rettype = add_types_resolver(c1->rettype, c2->rettype);
    return new Add<c1->rettype, c2->rettype, rettype>(c1, c2);
  }
}

其中add_types_resolver 是根据操作参数类型指定添加操作的返回类型的东西。

我们当然会失败,编译器会打我们的脸。我们不能将变量用作模板变量!这是因为在编译过程中实例化模板所需的所有信息都必须可用!


现在 - 问题。

除了编写大量 if-else 或 switch-case 语句之外,还有其他解决方案吗?我们不能以任何方式要求编译器在编译期间扩展所有情况吗?模板由枚举参数化,因此我们可以保证这样的过程是有限的。

请不要写出“我认为整个例子搞砸了”之类的回复。我只是想知道是否有一种方法可以为模板提供变量,知道它来自一个有限的小集合。

整个事情看起来有点矫枉过正,但我​​真的很好奇如何在这种不寻常的情况下实例化类。

【问题讨论】:

  • 整个例子搞砸了。模板在类型和编译时常量上参数化,而不是在运行时值上。但听起来你已经意识到这一点......
  • 您可以使用 int-to-type 习惯用法根据值生成模板的编译时类型,但是这是否是一个好主意完全是另一回事。
  • 将动态查找代码分解到程序的一个角落并使其在没有太多噪音的情况下可用并不难。
  • 您可以使用 boost::mpl 之类的东西让编译器为您生成所有 if/switch 案例。但这并不简单。

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


【解决方案1】:

使用宏的快速解决方案:

column_type.cc:

enum ColumnType {
  INT = 1,
  DOUBLE = 2,
  BOOL = 3
};

typed_call_test.cc(使用示例):

#include <iostream>
#include "column_type.cc"
#include "typed_call.cc"

template <ColumnType T>
void PrintType() {
  ::std::cout << T <<::std::endl;
}

int main() {
  ColumnType type = INT;
  // this won't compile:
  // PrintType<type>();                  
  // and instead of writing this:
  switch (type) {                  
    case INT:                  
      PrintType<INT>();                  
      break;                  
    case DOUBLE:                  
      PrintType<DOUBLE>();                  
      break;                  
    case BOOL:                  
      PrintType<BOOL>();                  
      break;                  
  }                  
  // now you can write this:
  TYPED_CALL(PrintType, type, );

  return 0;
}

typed_call.cc(“库”):

// Requirements:
// |function| return type must be void
//
// Usage:
//
// having for instance such |type| variable:
//   ColumnType type = INT;
// and such |foo| function definition:
//   template <ColumnType T>
//   void foo(t1 arg1, t2 arg2) {
//     …
//   }
//
// instead of writing (won't compile):
//   foo<type>(arg1, arg2);                  
// write this:
//   TYPED_CALL(foo, type, arg1, arg2);
//
//
// for foo with 0 arguments write this:
//   TYPED_CALL(foo, type, );
//
#define TYPED_CALL(function, type, args...) {                        \
  switch (type) {                                                    \
    case INT:                                                        \
      function<INT>(args);                                           \
      break;                                                         \
    case DOUBLE:                                                     \
      function<DOUBLE>(args);                                        \
      break;                                                         \
    case BOOL:                                                       \
      function<BOOL>(args);                                          \
      break;                                                         \
  }                                                                  \
}

#define BASE_TYPED_CALL(function, type, args...) {                   \
  switch (type) {                                                    \
    case INT:                                                        \
      function<int>(args);                                           \
      break;                                                         \
    case DOUBLE:                                                     \
      function<double>(args);                                        \
      break;                                                         \
    case BOOL:                                                       \
      function<bool>(args);                                          \
      break;                                                         \
  }                                                                  \
}

要将此解决方案“升级”,您可以将宏替换为函数(仍包含类似的开关构造)。但可能你想传递一个仿函数(带有 () 运算符的对象)作为这个函数的参数,而不是像这个宏中的普通函数。顺便说一句:这正是他们在 Google 中的做法。


第一个旁注:向华沙大学列式和分布式数据仓库课程的同学问好!本课程产生了许多令人费解的 C++ 模板问题:)

第二个旁注:这是我的 Typetraits 的等效项:

template <ColumnType T>
struct EnumToBuiltin {
};

template <>
struct EnumToBuiltin<INT> {
  typedef int type;
};
template <>
struct EnumToBuiltin<DOUBLE> {
  typedef double type;
};
template <>
struct EnumToBuiltin<BOOL> {
  typedef bool type;
};


template <typename T>
struct BuiltinToEnum {
};

template <>
struct BuiltinToEnum<int> {
  static const ColumnType type = INT;
};
template <>
struct BuiltinToEnum<double> {
  static const ColumnType type = DOUBLE;
};
template <>
struct BuiltinToEnum<bool> {
  static const ColumnType type = BOOL;
};

【讨论】:

    【解决方案2】:

    我们不能以任何方式要求编译器在编译期间扩展所有情况吗?

    你可以这样做。在它是动态的地方引入条件->TMP。如果你在正确的地方做,那么你将没有多少条件可以写。引入类型和常量作为编译时间信息将有助于减少这种情况。

    【讨论】:

      猜你喜欢
      • 2021-04-04
      • 1970-01-01
      • 1970-01-01
      • 2012-02-25
      • 1970-01-01
      • 2023-03-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多