calling a particular template function based on an enumerated value
Consider the following code where in I am calling a specific template function computecost
depending on an enumerated value (category). In call cases the arguments of computecost
are identical. There exists a one-one correspondence between the enum values and the C++ types. As the arguments to computecost
are always identical across all the calls, is it possible to write the following code more compactly, ie. without repeating for every type/enum value.
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_CLAS开发者_高级运维S: 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;
}
You could have a function that takes category
and returns an appropriate function pointer, which is then called with the appropriate arguments:
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);
Or using a map
, as Mark suggested:
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);
Firstly this all smells kinda funky to me (Type codes always scare me a bit) ... It feels like this should be some kind of virtual function in the prhs object whatever that is: .
Then your code would look like this
prhs->computecost(T, offT, Offset, CostMatrix );
If shifting computecost
into a member virtual function is not possible, then you'll be stuck with an ugly switch construct somewhere in your code... However if you find yourself doing the same thing over an over again and/or find it clutters that section of code, then hoist it out into a helper function
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;
}
}
Then your code just looks like this:
mxClassID category = mxGetClassID(prhs);
computecost(category, T,offT,Offset,CostMatrix );
Since each of the templated functions is treated by the compiler as a different function, there's no way to avoid having a different call for each one. You might be able to simplify by creating a table of function pointers, since each function has the same signature.
Is there any reason why you are not using dynamic dispatch for the computecost
function?
The simplest thing would be creating a inheritance hierarchy and just using dynamic dispatch. Each type in the hierarchy that would return mxINT8_CLASS
as class id would implement computecost
as a call to computecost<signed char>
, and similarly for all of the other combinations.
If there is a strong reason not to use dynamic dispatch, you might consider implementing your own dynamic dispatch in different ways. The most obvious, simple and probably easier to maintain is what you already have. Slightly more complex can be done with macros, or you can try a templated version just for fun...
The macro solution (next one in complexity) could use a macro to define the relationship, another to define each case
and then combine them:
#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
Combine:
switch ( category ) {
FORALL_IDS( CASE_DISPATCH_COMPUTECOST );
};
I have seen this done in the past, and don't like it, but if there is a good amount of places where you need to map from the category to the type that could be a simple to write hard to maintain solution. Also note that the FORALL_IDS
macro can be used to implement metaprogramming traits that map from the enum to the type and vice versa:
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
Note that this has a lot of drawbacks: macros are inherently unsafe, and this macros more so, as they define types and don't quite behave like functions, it is harder to find the appropriate amount of parenthesis to the arguments, which makes it more prone to all shorts of errors in text substitution... Macros don't know about contexts, so you might want to try and minimize the scope by undefining them right after use. Implementing the traits above is one good way to go about it: create the macro, use that to generate templated non-macro code, undef the macros. The rest of the code can use the templates rather than the macros to map from one to the other.
A different way of implementing dynamic dispatch is using a lookup table instead of the switch
statement above:
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
You can manually initialize the lookup table, or you can use a macro as above, the complexity of the code will not change much, just move from the calling side to wherever the lookup table is initialized. On the caller side you would just do:
lookup[ mxGetClassID(prhs) ]( T, offT, Offset, CostMatrix );
Instead of the switch
statement, but don't get fooled, the cost has not be removed, just pushed to the initialization (which might be good if you need to map more than one function, as you could create a struct of function pointers and perform the initialization of all at once, and there you have your own manually tailored vtable
, where instead of a vptr
you use the classId
field to index.
The templated version of this is probably the most cumbersome. I would try to implementing just for the fun of it, but not really use it in production code. You can try building the lookup table from a template[1], which is fun as an exercise but probably more complex than the original problem.
Alternatively, you can implement a type-list type of approach (A la Modern C++ Design) and in each one of the nodes dispatch to the appropriate function. This is probably not worth the cost, and will be a nightmare to maintain in the future, so keep away from it from production code.
To sum up:
Just use the language dynamic dispatch, that is your best option. If there is a compelling reason not too, balance the different options and complexities. Depending on how many places you need to perform the dispatch from classId to X (where X is computecost
here but could be many more things), consider using a hand tailored lookup table that will encapsulate all X
operations into a function table --note that at this point, whatever the motives to avoid the vtable
might have gone away: you are manually, and prone to errors implemented the same beast!
[1] The complexity in this case is slightly higher, because of the mapping from the enum to the types, but it should not be much more complex.
This is what macros are for.
#define COMPUTECOST(X) computecost<X>(T, offT, Offset, CostMatrix)
case mxINT8_CLASS: COMPUTECOST(signed char); break;
case mxUINT8_CLASS: COMPUTECOST(unsigned char); break;
...etc...
Saves you a little repetetive typing, but you still need to call each one individually.
精彩评论