基于枚举值调用特定模板函数

时间:2011-09-09 03:08:48

标签: c++ templates

考虑以下代码,我在调用特定模板函数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 个答案:

答案 0 :(得分:4)

你可以有一个函数接受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);

答案 1 :(得分:2)

首先,这对我来说有点时髦(类型代码总是吓到我一点)......感觉这应该是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 );

答案 2 :(得分:1)

由于每个模板化函数都被编译器视为不同的函数,因此无法避免对每个模板执行不同的调用。您可以通过创建函数指针表来简化,因为每个函数都具有相同的签名。

答案 3 :(得分:1)

您有没有理由不为computecost函数使用动态调度?

最简单的方法是创建一个继承层次结构,只使用动态调度。层次结构中将mxINT8_CLASS作为类ID返回的每个类型都会将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字段进行索引。

这个模板化的版本可能是最麻烦的。我会尝试实现它的乐趣,但不是真的在生产代码中使用它。您可以尝试building the lookup table from a template [1] ,这很有趣,但可能比原始问题更复杂。

或者,您可以实现 type-list 类型的方法(A la Modern C++ Design),并在每个节点中调度到相应的函数。这可能不值得花费,并且将来会成为一个噩梦,所以远离生产代码。

总结一下:

只需使用语言动态调度,这是您的最佳选择。如果没有令人信服的理由,请平衡不同的选项和复杂性。根据您需要执行从classId到X的调度的位置(此处X为computecost,但可能还有更多内容),请考虑使用手工定制的查找表来封装所有X个操作到功能表 - 注意,此时,无论避免vtable的动机可能已经消失:您手动容易出错实施了同样的野兽!

[1] 由于从枚举到类型的映射,这种情况下的复杂性略高,但它不应该复杂得多。

答案 4 :(得分:0)

这就是宏的用途。

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

为您省略一些重复输入,但您仍然需要单独调用每个。