How can I go from a runtime value to a template argument?
Background . I am writing several methods designed to be called on thousands of pixels in an image. The image can be in different pixel formats (8-bit grayscale, 16-bit RGB, 24-bit RGB, etc.). It would be inefficient to re-validate and insert into pixel format for every pixel, so I used C ++ templates to generate a version of the whole process for each supported pixel format at compile time:
// Note: PixelFormat::Enum is an integral type.
template<PixelFormat::Enum PixelFormat>
struct EdgeTracer {
Point FindInitialEdge(const void *pixels, int stride) {
/* [...] */
}
std::vector<Point> TraceEdge(
const void *pixels, int stride, const Point &initialEdge
) {
/* [...] */
}
};
Now I would like to create a wrapper for these methods as simple C functions (for DLL export to .NET P / Invoke). I am currently doing this:
EXPORT Point DLLAPI FindInitialEdge(
const void *pixels, int stride, PixelFormat::Enum pixelFormat
) {
switch(pixelFormat) {
case PixelFormat::Format8bppIndexed: {
return EdgeTracer<PixelFormat::Format8bppIndexed>::FindInitialEdge(
pixels, stride
);
}
case PixelFormat::Format16bppRgb565: {
return EdgeTracer<PixelFormat::Format16bppRgb565>::FindInitialEdge(
pixels, stride
);
}
case PixelFormat::Format24bppRgb888: {
return EdgeTracer<PixelFormat::Format24bppRgb888>::FindInitialEdge(
pixels, stride
);
}
default: {
// error handling
}
}
}
EXPORT PointBuffer *DLLAPI TraceEdge(
const void *pixels, int stride, const Point *initialEdge,
PixelFormat::Enum pixelFormat
) {
// Nearly the same switch statement all over again
}
Less optimal when you need to add support for a new pixel format, you need to touch on many unrelated features. Is there a sane way to avoid redundant operators switch
?
Here is what I have prepared so far, but I have not been able to make a variation version of it:
template<typename T>
void invokeTemplated(
PixelFormat::Enum pixelFormat, void (T::*method)(const void *pixels)
) {
switch(pixelFormat) {
case PixelFormat::Format16bppRgb555: {
T<PixelFormat::Format16bppRgb555> instance;
((&instance)->*method)(pixels);
break;
}
// [...]
}
}
source to share
-
write an abstract interface that mirrors your public interface, but only the internal one:
class Interface { public: virtual ~Interface() {} virtual Point FindInitialEdge(const void *pixels, int stride) = 0; // other pure virtual methods here };
-
create a global mapping from pixel type to objects that implement this interface
typedef std::unordered_map<PixelFormat::Enum, Interface*> ImplMap; ImplMap impls;
-
write all your public functions in the form:
EXPORT Point DLLAPI FindInitialEdge(const void *pixels, int stride, PixelFormat::Enum pixelFormat) { ImplMap::iterator i = impls.find(pixelFormat); if (i != impls.end()) { return i->FindInitialEdge(pixels, stride); } else { // error handling } }
-
now you can either inherit template
EdgeTracer
from inherit fromInterface
, or match virtual function definitions, or write template binding. The first is gaining a little less -
and finally, to register all concrete implementations, you just need one place to do something like
template <PixelFormat::Enum PF> bool register_format() { impls[PF] = new EdgeTracer<PF>; } bool initialized = register_format<PixelFormat::Format8bppIndexed>() | register_format<PixelFormat::Format16bppRgb565>() | register_format<PixelFormat::Format24bppRgb888>();
just make sure you use the value
initialized
somewhere so it can't be optimized and you have one place to centrally register new formats.
source to share
Some dirty work around
This is a pretty dirty job, but could you please try:
First:
You need to remind all classes
The definition can do the trick:
# define ALL_MY_CLASS A,B,C //ect....
Secondly:
All of your classes should have the same structure:
class A {// same for B, C ... public: static const PixelFormat :: enum e = PixelFormat :: Format8bppIndexed / * associated enum value * /; void methode_a (); // or even yours :: FindInitialEdge};
Then
You must review them.
int test_all_types_for_initial_edge( f) { // here you depack all your possible class
return (test_types_for_initial_edge<ALL_MY_CLASS>(f));
};
template<typename current, typename second, typename ...elses>
int test_types_for_initial_edge(PixelFormat::enum ee) {
if (ee == current ::e)//you test if the current tested class match the enum
return (EdgeTracer< current >::FindInitalEdge(/* do your stuff */);
return (test_types_for_initial_edge(ee));
}
template<typename current>
int test_types_for_initial_edge(PixelFormat::enum ee) {
if (ee == current ::e)//you test if the current tested class match the enum
return (EdgeTracer< current >::FindInitalEdge(/* do your stuff */);
return (DEFAULT_VALUE); //or throw whatever
}
What an improvement on this (rather dirty) workaround
I think a little sfinae and a pre-compil check (if the class is like a static_const enum ... or the desired method ... ect ...) might be nice ... But! If you want to update your code, all you have to do is change ALL_MY_CLASS
define!
By the way ...
I don't think to compile completely (which is mostly correct, but part is pseudocode), if you need other help (like adding type_traits and making it a little easier, just ask her ^^)
source to share
Perhaps the problem is "void * pixels". If you have a specific type for your pixels (8 bit, RGBA, etc.), you can remove the switcher and implement a function for each type.
If you use template functions, the compiler will automatically resolve this type when using a pointer and call the function you want.
source to share