Переменная enum как аргумент динамического шаблона - PullRequest
2 голосов
/ 03 апреля 2012

Предположим, что у нас есть перечислимый тип:

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? Разве мы не можем попросить компилятор раскрыть все случаи во время компиляции? Шаблон параметризован перечислением, поэтому у нас есть гарантия, что такой процесс конечен.

И, пожалуйста, не пишите ответы типа "Я думаю, что весь пример испорчен". Я просто хочу знать, есть ли способ заполнить шаблон переменной, зная, что он из конечного небольшого набора.

Все это может показаться излишним, но мне действительно любопытно, как я могу создавать экземпляры классов в таких необычных ситуациях.

Ответы [ 2 ]

2 голосов
/ 06 апреля 2012

Быстрое и грязное решение с макросами:

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.


1-е лицо: привет моему однокласснику из курса «Колонка и распределенные хранилища данных» в Варшавском университете! Этот курс порождает много сногсшибательных шаблонных вопросов C ++:)

2-й sidenote: вот как выглядит мой эквивалент 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;
};
0 голосов
/ 03 апреля 2012

Разве мы не можем попросить компилятор каким-либо образом расширить все случаи во время компиляции?

Вы можете сделать это. Введите условие-> TMP, где оно является динамическим. Если вы делаете это в нужных местах, то у вас будет мало условных текстов, которые можно написать. Введение типов и констант в качестве информации о времени компиляции поможет минимизировать это.

...