вызов определенной шаблонной функции на основе перечисляемого значения - PullRequest
5 голосов
/ 09 сентября 2011

Рассмотрим следующий код, в котором я вызываю определенную шаблонную функцию computecost в зависимости от перечисляемого значения (категории). В случаях вызова аргументы computecost идентичны. Существует однозначное соответствие между значениями перечисления и типами C ++. Поскольку аргументы computecost всегда одинаковы для всех вызовов, возможно ли написать следующий код более компактно, т.е. без повторения для каждого значения типа / перечисления.

mxClassID category = mxGetClassID(prhs);
    switch (category)  {
     case mxINT8_CLASS:   computecost<signed char>(T,offT,Offset,CostMatrix);   break;
     case mxUINT8_CLASS:  computecost<unsigned char>(T,offT,Offset,CostMatrix);  break;
     case mxINT16_CLASS:  computecost<signed short>(T,offT,Offset,CostMatrix);  break;
     case mxUINT16_CLASS: computecost<unsigned short>(T,offT,Offset,CostMatrix); break;
     case mxINT32_CLASS:  computecost<signed int>(T,offT,Offset,CostMatrix);  break;
     case mxSINGLE_CLASS: computecost<float>(T,offT,Offset,CostMatrix); break;
     case mxDOUBLE_CLASS: computecost<double>(T,offT,Offset,CostMatrix); break;
     default: break;
    }

Ответы [ 5 ]

4 голосов
/ 09 сентября 2011

У вас может быть функция, которая принимает category и возвращает соответствующий указатель функции, который затем вызывается с соответствующими аргументами:

decltype(&computecost<int>) cost_computer(mxClassID const category) {
    switch (category) {
        case mxINT8_CLASS: return &computecost<signed char>;
        ...
    }
}

cost_computer(mxGetClassID(prhs))(T, offT, Offset, CostMatrix);

Или используя map, как предложил Марк:

std::map<mxClassID, decltype(&computecost<int>)> compute_functions =
    boost::assign::map_list_of
        (mxINT8_CLASS, &computecost<signed char>)
        // ... and so on
compute_functions[mxGetClassID(prhs)](T, offT, Offset, CostMatrix);
2 голосов
/ 09 сентября 2011

Во-первых, все это пахнет довольно странно (коды типов меня всегда пугают) ... Такое ощущение, что это должна быть какая-то виртуальная функция в объекте prhs, что бы это ни было:.

Тогдаваш код будет выглядеть следующим образом

prhs->computecost(T, offT, Offset, CostMatrix );

Если переключение computecost в виртуальную функцию-член невозможно, тогда вы застрянете в вашем коде с некрасивой конструкцией переключателя ... Однако если выпоймать себя на том, что снова и снова делать то же самое, и / или найти, что он загромождает этот фрагмент кода, а затем поднять его в вспомогательную функцию

void computecost( mxClassID category, /* all the other args go here */ )
{
  switch (category)  {
   case mxINT8_CLASS:   computecost<signed char>(T,offT,Offset,CostMatrix);   break;
   case mxUINT8_CLASS:  computecost<unsigned char>(T,offT,Offset,CostMatrix);  break;
   case mxINT16_CLASS:  computecost<signed short>(T,offT,Offset,CostMatrix);  break;
   case mxUINT16_CLASS: computecost<unsigned short>(T,offT,Offset,CostMatrix); break;
   case mxINT32_CLASS:  computecost<signed int>(T,offT,Offset,CostMatrix);  break;
   case mxSINGLE_CLASS: computecost<float>(T,offT,Offset,CostMatrix); break;
   case mxDOUBLE_CLASS: computecost<double>(T,offT,Offset,CostMatrix); break;
   default: break;
   }
}

Тогда ваш код выглядит так:

mxClassID category = mxGetClassID(prhs);
computecost(category, T,offT,Offset,CostMatrix );
1 голос
/ 09 сентября 2011

Есть ли причина, по которой вы не используете динамическую диспетчеризацию для функции computecost?

Самое простое было бы создать иерархию наследования и просто использовать динамическую диспетчеризацию. Каждый тип в иерархии, который будет возвращать mxINT8_CLASS в качестве идентификатора класса, будет реализовывать computecost в качестве вызова computecost<signed char>, и аналогично для всех других комбинаций.

Если есть веская причина не использовать динамическую диспетчеризацию, вы можете рассмотреть возможность реализации собственной динамической диспетчеризации различными способами. Самое очевидное, простое и, вероятно, более простое в обслуживании - это то, что у вас уже есть. Чуть сложнее можно сделать с помощью макросов, или вы можете попробовать шаблонную версию просто для удовольствия ...

Макро-решение (следующее по сложности) может использовать макрос для определения отношения, другой для определения каждого case, а затем объединить их:

#define FORALL_IDS( macro ) \
   macro( mxINT8_CLASS, signed char ); \
   macro( mxUINT8_CLASS, unsigned char ); \
// ...

#define CASE_DISPATCH_COMPUTECOST( value, type ) \
   case value: computecost<type>( T, offT, Offset, CostMatrix ); break

Объединить:

switch ( category ) {
   FORALL_IDS( CASE_DISPATCH_COMPUTECOST );
};

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

template <classId id>
struct type_from_id;
#define TYPE_FROM_ID( id, T ) \
   template <> struct type_from_id<id> { typedef T type; }
FORALL_IDS( TYPE_FROM_ID );
#undef TYPE_FROM_ID

template <typename T>
struct id_from_type;
#define ID_FROM_TYPE( id, T ) \
   template <> struct id_from_type<T> { static const classId value = id; }
FORALL_IDS( ID_FROM_TYPE );
#undef ID_FROM_TYPE

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

Другим способом реализации динамической диспетчеризации является использование таблицы поиска вместо приведенного выше оператора switch:

typedef T function_t( T1, T2, T3 ); // whatever matches the `computecost` signature
function_t *lookup[ categories ];   // categories is 1+highest value for the category enum

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

lookup[ mxGetClassID(prhs) ]( T, offT, Offset, CostMatrix );

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

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

В качестве альтернативы вы можете реализовать тип-подхода тип подхода (A la Modern C ++ Design ) и в каждом из узлов отправлять на соответствующая функция. Это, вероятно, не стоит затрат и в будущем станет кошмаром для обслуживания, поэтому держитесь подальше от производственного кода.

Подводя итог:

Просто используйте динамическую диспетчеризацию языка, это ваш лучший вариант. Если есть не слишком веская причина, сбалансируйте различные варианты и сложности. В зависимости от того, сколько мест вам нужно выполнить диспетчеризацию от classId до X (где X здесь computecost, но может быть гораздо больше), рассмотрите возможность использования ручной таблицы поиска, которая будет инкапсулировать все операции X в таблицу функций - отметим, что в этот момент мотивы избегания vtable могли бы исчезнуть: вы вручную и подвержены ошибкам реализовали одного и того же зверя!

[1] Сложность в этом случае несколько выше из-за отображения перечисления в типы, но она не должна быть намного более сложной.

1 голос
/ 09 сентября 2011

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

0 голосов
/ 09 сентября 2011

Это то, для чего нужны макросы.

#define COMPUTECOST(X) computecost<X>(T, offT, Offset, CostMatrix)
case mxINT8_CLASS:   COMPUTECOST(signed char);   break;
case mxUINT8_CLASS:  COMPUTECOST(unsigned char);  break;
...etc...

Сохраняет небольшую повторяющуюся печать, но вам все равно нужно вызывать каждую из них по отдельности.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...