4

Consider the following code

#include <iostream>

enum MyEnum{
    A,    
    B,
    END
};

template <int N>
class Trait {};

template<>
class Trait<A> {
    public:
        static int funct(int i) {return i*3;}
};

template<>
class Trait<B> {
    public:
        static int funct(int i) {return i*24;}
};


using namespace std;

int main(){
    int i = 1;
    switch(i){
        case A: cout << Trait<A>::funct(i) << endl; break;
        case B: cout << Trait<B>::funct(i) << endl; break;
    }   
} 

Which will print 24 on the screen.

Assume now that I have many more values in the enum and that I define all the corresponding template specializations of the class Trait.

To avoid writing all the code necessary in the switch statement I wrote a REPEAT macro which works almost like I want:

#include <iostream>

#define REPEAT(N, macro) REPEAT_##N(macro)
#define REPEAT_0(macro)
#define REPEAT_1(macro) REPEAT_0(macro) macro(0)
#define REPEAT_2(macro) REPEAT_1(macro) macro(1)
#define REPEAT_3(macro) REPEAT_2(macro) macro(2)
#define REPEAT_4(macro) REPEAT_3(macro) macro(3)
// etc...

// enum and class definitions

int main(){
   #define MY_MACRO(N) case N: cout << Trait<N>::funct(i) << endl; break;

   switch(i){
      REPEAT(2, MY_MACRO)
   }
}

The problem I have with this approach is that I cannot use

REPEAT(END, MY_MACRO)

because the preprocessor doesn't know about my enum.

Question: Is there a way to generate automatically the switch statement?

Notes:

  • The situation where I have to use this is much more complicated and having something automated would be really helpful.
  • It is important for me to use a switch statement because of the speed which can be achieved (speed is crucial for my application).

Thanks!

EDIT 1

More notes:

  • It is important that the generation of the switch depends on the value of END defined in the enum.

EDIT 2/3

I decided to make an addition here to explain better my application and why I prefer some solutions to others

  • In my real application the enum contains almost 50 different values and it will be extended in the future (hopefully by other people). The enum contains consecutive values.
  • the class "Trait" has more than 1 member function (currently 5). Furthermore, I need to use all this in 5 different files. If I don't use an automated way of generating what I need I end up writing many times code which is basically the same.
  • the member functions of Trait are used in the same way all the times.
  • currently, inside my switch I have a function call which looks like this (in1, in2 and out are all double passed by reference, const for the first two cases).

    case A: Trait::funct(in1, in2, out); break;

Why do I like templates?

Consider the case Trait has 2 functions funct1 and funct2. I could define

template <int N>
class Trait {
    public:
        static int funct1(int i){static_assert(N!=N, "You forgot to define funct1");}
        static int funct2(int i){static_assert(N!=N, "You forgot to define funct2");}
};

Now, if a function definition is missing, the compiler will return a meaningful error. When other people will make additions this will be helpful.

Using the method based on C++11 features suggested by Jarod42 I can avoid maintaining long arrays of function pointers which would be error prone.

Speed tests

So far I experimented with 3 solutions but with only two member functions in Trait:

  • the solution suggested by Jarod42
  • a simple array of function pointers as suggested by nndru and Ali
  • switch statement with the RETURN macro

The first two solutions seem to be equivalent, while the one based on the switch is 5 times faster. I used gcc version 4.6.3 with the flag -O3.

carlo
  • 325
  • 1
  • 4
  • 12
  • Hi. Why you don't use a map between your enum member and some kind of functor assigned with it. – nndru Jan 17 '14 at 12:45
  • As I wrote in my notes, speed is really important for me. I think using a map would make the code slower. – carlo Jan 17 '14 at 12:48
  • Map is a quick searcher container, I think is a good Idea :D – Netwave Jan 17 '14 at 12:53
  • I know that a map is quick, but a switch statement is usually compiled to a jump table which is much faster. – carlo Jan 17 '14 at 12:54
  • 1
    Ok, against map you can use an array: assign enum members with integers and store functors in array where enum id will be array cell id. – nndru Jan 17 '14 at 13:12
  • Why not virtual functions? – Luka Rahne Jan 17 '14 at 13:16
  • @nndru In my real code I have more functions inside the class Trait and several switches, so an array would be more difficult to maintain – carlo Jan 17 '14 at 13:31
  • @Luka Rahne From experiments virtual functions are slower – carlo Jan 17 '14 at 13:32
  • @carlo ok it's up to you, but you can wrap and hide array items registration with some kind of function or macros, and put them in right places. I think that will be equal to your macros approach. – nndru Jan 17 '14 at 14:06
  • @carlo I don't see why the array approach is more difficult to maintain than the array of functions pointers. Please update the question and show your real use case. – Ali Jan 17 '14 at 14:34
  • @Ali I added some more details and the results of the preliminary tests I made – carlo Jan 17 '14 at 16:28
  • @carlo OK. (1) Please add details how you currently use the 5 different functions at the `case MyEnum` labels. It doesn't really matter how many functions you have; what matters is what happens at the `case` labels. (2) If you already have 50 cases and it is likely to become more, you want binary_search on a sorted array of pointers (that is O(log(n)) time instead of O(n)). This will win over the switch statement sooner or later. So, please add more details to your question. – Ali Jan 17 '14 at 16:52
  • @carlo It is not clear to me how you define what happens at `case X: /* How do you generate the content here? */ break;`. Because you have to define it once, no matter how you do it. **I would like to understand your maintenance concerns.** With the array approach (that I would like to show you), instead of `case X: f_x(); break;` you need to write `{ X, f_x() }` and that is it. I don't see how the number of member functions in the trait class matters. – Ali Jan 17 '14 at 17:26
  • @Ali I added more details on how my switch cases look like. My only concern about maintainability is about writing down explicitly 5 different function pointer arrays (if I don't automate this). – carlo Jan 17 '14 at 17:54
  • @carlo Ah, I see. In that case, you can still use a macro to generate all five arrays from one declaration. OK, please give me some time, I will do some speed tests myself, I will make sure that the array approach is *faster* than the `switch` approach, and get back to you with suggestions. Please be patient. – Ali Jan 17 '14 at 19:04
  • @carlo OK, I have added a possible solution with macros and also the results of my own speed test. The function pointer array approach is faster. Please read my updated answer. – Ali Jan 17 '14 at 23:12

6 Answers6

2

In C++11, you may do the following:

#if 1 // Not in C++11
#include <cstdint>

template <std::size_t ...> struct index_sequence {};

template <std::size_t I, std::size_t ...Is>
struct make_index_sequence : make_index_sequence < I - 1, I - 1, Is... > {};

template <std::size_t ... Is>
struct make_index_sequence<0, Is...> : index_sequence<Is...> {};

#endif

namespace detail {

template <std::size_t ... Is>
int funct(MyEnum e, int i, index_sequence<Is...>)
{
    // create an array of pointer on function and call the correct one.
    return std::array<int(*)(int), sizeof...(Is)>{{&Trait<MyEnum(Is)>::funct...}}[(int)e](i);
}

} // namespace detail

int funct(MyEnum e, std::size_t i)
{
    return detail::funct(e, i, make_index_sequence<std::size_t(END)>());
}

Note: enum should not have hole (so here A=0 and B=1 is ok)

Following macro may help:

#define DYN_DISPATCH(TRAIT, NAME, SIGNATURE, ENUM, ENUM_END) \
    namespace detail { \
    template <std::size_t ... Is> \
    constexpr auto NAME(ENUM e, index_sequence<Is...>) -> SIGNATURE \
    { \
        return std::array<SIGNATURE, sizeof...(Is)>{{&TRAIT<ENUM(Is)>::NAME...}}[(int)e]; \
    } \
    } /*namespace detail */ \
    template <typename...Ts> \
    auto NAME(ENUM e, Ts&&...ts) \
        -> decltype(std::declval<SIGNATURE>()(std::declval<Ts>()...)) \
    { \
        return detail::NAME(e, make_index_sequence<std::size_t(ENUM_END)>())(std::forward<Ts>(ts)...); \
    }

And then use it as:

    DYN_DISPATCH(Trait, funct, int(*)(int), MyEnum, END)

    // now `int funct(MyEnum, int)` can be call.
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I would make the array `static const` to make it clear to the compiler I did not want to reconstruct it each time. Then I would performance test both versions. – Yakk - Adam Nevraumont Jan 17 '14 at 13:40
  • Frankly I can't wrap my head around the usefullness of such solutions. having to copy-paste such monstruosities as `return std::array{{&Trait::funct...}}[(int)e](i);` each time you want to define another "automatic switch" is definetly not worth the effort, IMHO – kuroi neko Jan 17 '14 at 13:43
  • Actually I'm using C++11 also if I'm quite new to it... I will try your code. Do you think it will compile into a jump table? – carlo Jan 17 '14 at 13:45
  • @Jarod42 If his enum is contiguous, a plain old function pointer is sufficient, see my answer. – Ali Jan 17 '14 at 13:54
  • @Ali: I build the array more automatically. – Jarod42 Jan 17 '14 at 13:59
  • @kuroineko: I have just added a macro helper to avoid to repeat the template stuff. – Jarod42 Jan 17 '14 at 14:03
  • @carlo: I hope compiler may do that since it has already the array of function pointers. – Jarod42 Jan 17 '14 at 14:07
  • 2
    Frankly, all this code for moving a matchstick is not worth the effort IMHO. As I see it, if the aim is to avoid copy-pasting a bit of code for each of the enum values, that's a job the preprocessor can do more easily and efficiently. Besides, this makes no sense unless you want to optimize your execution speed (or else you could use virtual classes for a start), and what's hidden behind your template is an array of function pointers, suboptimal compared to a plain hard-coded switch. – kuroi neko Jan 17 '14 at 14:07
  • @Jarod42 Well, you do it now; you didn't when I left the comment. – Ali Jan 17 '14 at 14:08
  • @Jarod42 I made some test (see EDIT 2) and it look like the switch method is faster – carlo Jan 17 '14 at 16:34
  • @carlo: So I suggest to use kuroi neko's solution (And you can also remove `END`). – Jarod42 Jan 18 '14 at 10:35
2

As you say, your enum is contiguous. In that case you don't need any templates or std::map or switch:

Use simply an array of function pointers and the enum as the index into the function pointer array!

#include <cassert>
#include <cstdio>

enum {
  A,
  B,
  SIZE
};

int A_funct(int i) { return 3*i; }

int B_funct(int i) { return 24*i; }

typedef int (*enum_funct)(int );

enum_funct map[] = { A_funct, B_funct };

// In C++11 use this:
//static_assert( sizeof(map)/sizeof(map[0])==SIZE , "Some enum is missing its function!");

int main() {
  assert(sizeof(map)/sizeof(map[0])==SIZE && "Some enum is missing its function!");
  int i = 1;
  std::printf("case A prints %d\n", map[A](i) );
  std::printf("case B prints %d\n", map[B](i) );
}

UPDATE: From your comments:

My only concern about maintainability is about writing down explicitly 5 different function pointer arrays (if I don't automate this).

OK, now I understand the maintenance concern.

I believe you can only achieve this (no matter whether you use function pointer arrays or the switch approach) if you use some sort of source code generation, either with macros or write your own source code generator. You also have to work out some naming conventions so that the function pointer arrays (or the code at the case statements in the switch approach) can be automatically generated.

Since you didn't specify it, I just made up my own naming convention. If you are comfortable with macros, here is what I hacked together with the Boost Preprocessor Library by some mindless editing of the example:

#include <boost/preprocessor/repetition.hpp>

#define ENUM_SIZE 2

#define ENUM(z, n, unused) e##n,
enum { 
  BOOST_PP_REPEAT(ENUM_SIZE, ENUM, ~)  
  SIZE
};
#undef ENUM

int fA_e0(int i) { return 3*i; }
int fA_e1(int i) { return 24*i; }

int fB_e0(int i) { return 32*i; }
int fB_e1(int i) { return  8*i; }

typedef int (*enum_funct)(int );

#define MAP(z, n, case) f ## ##case ## _e##n,

enum_funct map_A[] = {
  BOOST_PP_REPEAT(ENUM_SIZE, MAP, A)
};

enum_funct map_B[] = {
  BOOST_PP_REPEAT(ENUM_SIZE, MAP, B)
};

#undef MAP

Here is what we get after the preprocessor resolved these macros (g++ -E myfile.cpp):

enum { e0, e1, SIZE };

[...]

typedef int (*enum_funct)(int );

enum_funct map_A[] = {
  fA_e0, fA_e1,
};

enum_funct map_B[] = {
  fB_e0, fB_e1,
};

So, as you can see, if you specify your own naming conventions, you can automatically generate the maps (function pointer arrays). The documentation is good.

However, if I were you, I would write my own source code generator. I would specify a simple text file format (key - value pairs on one line, separated by white space) and write my own tool to generate the desired C++ source files from this simple text file. The build system would then invoke my source code generator tool in the pre-build step. In that way, you don't have to mess with macros. (By the way, I wrote a little testing framework for myself and to circumvent the lack of reflection in C++ I use my own source code generator. Really not that difficult.)


The first two solutions seem to be equivalent, while the one based on the switch is 5 times faster. I used gcc version 4.6.3 with the flag -O3.

I would have to see your source code, the generated assembly and how you measured the time in order to understand how that happened.

So I also did my own speed tests. Since it would clutter this answer, the source codes are here: switch approach and the function pointer array approach.

As I expected: the switch approach is faster but only if you have a handful of branches. Andrei Alexandrescu also says the same in his talk Writing Quick Code in C++, Quickly, at around 38 min. On my machine, the switch approach is as fast as the function pointer array approach if the enum size is 5. If the enum size is bigger than 5, the function pointer array approach is consistently faster. If the enum size is 200 and there are 10^8 function invocations, it is more than 10% faster on my machine. (The online codes have only 10^7 function invocations otherwise it times out.)

(I used link time optimization (-O3 -flto flag both to the compiler and the linker) and I can only recommend it; it gives a nice performance boost (in my codes up to 2.5x) and the only thing you need to do is to pass one extra flag. However, in your case the code was so simple that it didn't change anything. If you wish to try it: The link time optimization is either not available or only experimental in gcc 4.6.3.)


From your comments:

I made new experiments following step by step your benchmark method but I still get better results with the switch statement (when the enum size is 150 the switch is still almost twice as fast as than the solution with pointers). [...] In the test with my code the switch method performs always better. I run also some experiments with your code and I got the same kind of results you got.

I have looked at the generated assembly codes, having at least 5 functions (5 cases). If we have at least this many functions, roughly speaking, what happens is that the compiler turns the switch approach into the function pointer approach with one significant disadvantage. Even in the best case, the switch always goes through 1 extra branch (integer comparison potentially followed by a jump) compared to the hand-coded function pointer array approach when dispatching to the function to be called. This extra branch belongs to the default: label which is generated even if you deliberately omit it in the C++ code; there is no way to stop the compiler from generating the code for this. (If you have at most 4 cases and all 4 function calls can be inlined, then it is different; however you already have 50 cases so it doesn't matter.)

Apart from that, with the switch approach, additional (redundant) instructions and paddings are generated, corresponding to the code at the case: labels. This potentially increases your cache misses. So, as I see it, the the switch is always inferior to the function pointer approach if you have more than a handful of cases (5 cases on my machine). That is what Andrei Alexandrescu says in his talk too; he gives a limit of ~7 cases.

As for the reasons why your speedtests indicate the opposite: These sort of speed testings are always unreliable because they are extremely sensitive to alignment and caching. Nevertheless, in my primitive tests, the switch approach was always slightly worse than the function pointer array, which is in agreement with my above analysis of the assembly codes.

Another advantage of the function pointer arrays is that it can be built and changed at runtime; this is something that you don't get with the switch approach.

The weird thing is that the speed I get with the function pointer array changes depending on the enum size (I would expect it to be roughly constant).

As the enum size grows, you have more functions and the instruction cache misses are more likely to happen. In other words, the program should run slightly slower if you have more functions. (It does on my machine.) Of course the whole thing happens at random, so there will be significant deviations, don't be surprised if it runs faster for ENUM_SIZE=42 than for 41. And as mentioned earlier, alignment adds additional noise to this.

Ali
  • 56,466
  • 29
  • 168
  • 265
  • Using a switch might allow the compiler to perform further optimizations (for instance if some of the enum values are calling the same functions), but these kind of optimizations are usually useless and lost in background noise. I find your solution the most maintenable and elegant. – kuroi neko Jan 17 '14 at 13:59
  • @kuroineko Yes. Although the `switch` statement can become inferior if there are many cases and they are all different. Then the array approach is likely to be faster. If I remember correctly "many" is greater than 7 in this case which really isn't that many; [Andrei Alexandrescu mentioned this](http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly) in his talk. – Ali Jan 17 '14 at 14:07
  • Indeed. I was more thinking of sparsely populated arrays, like when you want to handle edge cases for a few values. Frankly, all this question seems fundamentally wrong to me. Looks like the OP is asking for a generic way of optimizing code, which is a contradiction in terms. – kuroi neko Jan 17 '14 at 14:12
  • @kuroineko Yes, I get your point and I totally agree. My comment was trying to say that in some other cases the array approach can be the faster one. *"Want speed? Measure. (by Howard Hinnant)"* It applies to this case as well. One would have to do careful profiling to see if it really matters at all which approach is picked. – Ali Jan 17 '14 at 14:31
  • exactly. This "automatic switch" is more like a solution looking for a problem :) – kuroi neko Jan 17 '14 at 14:36
  • @Ali Thanks a lot for your effort in answering the question! I made new experiments following step by step your benchmark method but I still get better results with the switch statement (when the enum size is 150 the switch is still almost twice as fast as than the solution with pointers). The weird thing is that the speed I get with the function pointer array changes depending on the enum size (I would expect it to be roughly constant). – carlo Jan 18 '14 at 10:19
  • @carlo Do you find the switch *always* better or only at 150? The benchmark is extremely sensitive to [alignment](http://stackoverflow.com/a/19570226/341970) and caching. A lucky alignment in the switch benchmark and an unlucky for the function pointer can explain if the switch is sometimes faster than function pointers. If you find the switch always faster then I have to ask: Do you run *exactly* the same source I posted or did you make any changes? In any case, you can see now how to generate the switch statement automatically with `BOOST_PP_REPEAT`, so you can use switch if you wish. – Ali Jan 18 '14 at 12:09
  • @Ali In the test with my code the switch method performs always better. I run also some experiments with your code and I got the same kind of results you got. I will investigate further since my code is really similar to yours... – carlo Jan 18 '14 at 13:42
  • @carlo OK, I wrote a big update. Long story short: The compiler turns the switch approach into the function pointer approach if you have at least 5 cases but there is a significant disadvantage. Please read the updated answer. – Ali Jan 18 '14 at 19:12
  • @Ali I did more test to understand the different results I got with your code and with mine. If I modify you code with the switch approach and I remove the function execute, the switch gets faster. I removed the line `dummy += execute(a[i], a[i]);` and added `#define CASES(z, n, unused) case n: dummy += f_##n( a[i] ); break;` followed by `switch (a[i]) { BOOST_PP_REPEAT(ENUM_SIZE, CASES, ~) }` and `#undef CASES` (sorry for the bad formatting) – carlo Jan 20 '14 at 10:10
  • @carlo OK, I see what changes you did. Unfortunately, I don't have time to look into this during the week, I have a deadline. I promise I will get back to this problem; please give me some time. In the meantime: how much faster is the `switch` after your changes? What `ENUM_SIZE` have you tested? – Ali Jan 20 '14 at 10:45
  • @Ali ENUM_SIZE is 150. The ratio time_with_pointer_array / time_with_switch_method is 1.18. Thank you! – carlo Jan 20 '14 at 10:50
  • @carlo If the difference is just 18% then it might be simply due to alignment, see http://stackoverflow.com/q/19470873/341970 for example. If the `switch` is *accidentally* aligned in a lucky way and the function pointers unluckily, that explains everything. *But this is accidental.* As far as I can tell from analyzing the assembly codes, the `switch` results in a slightly worse code even in the best case (an extra integer comparison and potentially an extra jump). Of course, an accidental lucky alignment may overcompensate the extra work, making the slower code look faster. – Ali Jan 20 '14 at 11:01
  • @carlo Well, actually there is one thing I will do. With the `-falign-*` flags, I can align both in the same way so that there is no difference in alignment between the two cases. In that way we can do a fair comparison; and you will know how to compile your code (which `-falign-*` flag to use and how) so that it runs as fast as possible. – Ali Jan 20 '14 at 11:04
1

You don't need templates at all to do this. More like good old X macros

#define MY_ENUM_LIST VAL(A) VAL(B)

// define an enum
#define VAL(x) x,
enum MyEnum { MY_ENUM_LIST END };
#undef VAL

// define a few functions doing a switch on Enum values  

void do_something_with_Enum (MyEnum value, int i)
{
   switch (value)
   {
      #define VAL(N) case N: std::cout << Trait<N>::funct(i) << std::endl; break;
      MY_ENUM_LIST
      #undef VAL
   }
}

int do_something_else_with_Enum (MyEnum value)
{
   switch (value)
   {
      #define VAL(x) case x: yet_another_template_mayhem(x);
      MY_ENUM_LIST
      #undef VAL
   }
}

I've wasted enough time with this already. If you think templates are the solution, simply change your question to "templates experts only, preprocessor not good enough" or something.

You will not be the first wasting your time on useless templates. Many people make a fat living on providing bloated, useless solutions to inexisting problems.

Besides, your assumption of a switch being faster than an array of function pointers is highly debatable. It all depends on the number of values in your enum and the variability of the code inside your case statements.

Now if optimization is not such a big issue, you can simply use virtual methods to specialize the behaviour of whatever objects are selected by your enum and let the compiler handle the whole "automatic switch" stuff for you.

The only benefit of this approach is to avoid duplicating code if your objects are similar enough to make you think you will do a better job than the compiler handling them in a specialized way.

What you seem to be asking for is a generic solution for optimizing an unknown code pattern, and that is a contradiction in terms.

EDIT: thanks to Jarod42 for cleaning up the example.

Jarod42
  • 203,559
  • 14
  • 181
  • 302
kuroi neko
  • 8,479
  • 1
  • 19
  • 43
  • Using my method I manage to execute really different functions inside the switch statement. I cannot see how I can achieve the same behavior with your method. – carlo Jan 17 '14 at 13:08
  • Well simply define CASEVAL to do whatever function call you need. Or is there another catch? – kuroi neko Jan 17 '14 at 13:10
  • I need that every single case inside the switch statement executes a different code. Furthermore, I'd need that the switch statement is generated using the value of END defined inside the enum. – carlo Jan 17 '14 at 13:15
  • Your question is "***C++ automatic generation of switch statement***". I showed you a way to do just that. The contents of the switch can be whatever you want, but that's another question. If you ask me, doing automated code generation is a straight way to hell if you don't draw a line between data unicity and readability soon. But that's just an opinion. – kuroi neko Jan 17 '14 at 13:23
  • I started my question with some code to explain where I want to use the automatic generation. I need many switches which are slightly different and I think that writing them by hand would be too error prone. – carlo Jan 17 '14 at 13:34
  • Man, I'm not getting paid for this, you know. You said your problem was the preprocessor did not know about your enum. Use the method I showed you and it will. The rest is another question entirely. – kuroi neko Jan 17 '14 at 13:37
  • An answer that addresses the title but ignores the body of the question is not a good answer... To use the above technique, the OP would have to duplicate every `enum` value name in the macro, correct? – Yakk - Adam Nevraumont Jan 17 '14 at 13:43
  • @Yakk No he wouldn't. See my edit. But yet again this was not part of the question. Looks more like the OP is convinced a template-less solution cannot work, so a mere preprocessor solution is rejected before thinking of what it could offer. – kuroi neko Jan 17 '14 at 13:56
  • You are missing an `#undef` – Yakk - Adam Nevraumont Jan 17 '14 at 14:11
  • @Yak Since my answer was rejected anyway, I leave the correction to template freaks as a trivial exercise :). – kuroi neko Jan 17 '14 at 14:15
  • @kuroineko: Your (C-)solution is good (and LLVM use it for there visitors). But you don't use the same enum that the OP, neither the same body, so it more complicate to transpose to OP case. – Jarod42 Jan 17 '14 at 14:25
  • @Jarod42 frankly if the OP is not able to transpose this, he's better off not using the technique at all. This is dangerous stuff, and using it without a pretty good understanding of what is going on behind the scene would get you up sh*t creek with no paddle in no time. – kuroi neko Jan 17 '14 at 14:28
0

It looks like you would like to associate and integer id with each function and find functions by the id.

If your id's are sequential you can have an array of function pointers indexed by that id, which would give you O(1) lookup complexity, e.g.:

typedef int Fn(int);

enum FnId {
    A,
    B,
    FNID_COUNT
};

int fn_a(int);
int fn_b(int);

Fn* const fns[FNID_COUNT] = {
    fn_a,
    fn_b
};

int main() {
    fns[A](1); // invoke function with id A.
}

If the id's are not sequential, you can still have a sorted array of {id, function_ptr} tuples and do binary search on it, O(lg(N)) lookup complexity.

None of these require macros or templates.

Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
0

For numeric (database) type identifiers I have a template holding the identifiers. A dispatch via variadic templates calls a functor with the matching type traits:

#include <iostream>
#include <stdexcept>

// Library
// =======

class TypeIdentifier
{
    public:
    typedef unsigned Integer;

    enum Value
    {
        Unknown,
        Bool,
        Int8,
        UInt8,
        Int16,
        UInt16,
        Int32,
        UInt32,
        Int64,
        UInt64,
        Float,
        Double,
        String,
        LargeObject,
        Date,
        Time,
        DateTime
    };

    template <Value ...Ids>  struct ListType {};
    typedef ListType<
        Bool,
        Int8,
        UInt8,
        Int16,
        UInt16,
        Int32,
        UInt32,
        Int64,
        UInt64,
        Float,
        Double,
        String,
        LargeObject,
        Date,
        DateTime,
        // Always the last value:
        Unknown
    >
    List;

    public:
    TypeIdentifier(Integer value = Unknown)
    :   m_id(value)
    {}

    Integer id() const { return m_id; }

    /// Involve a functor having a member function 'Result apply<Traits>()'.
    template<typename Functor>
    typename Functor::result_type dispatch(const Functor&);

    private:
    Integer m_id;
};

template<TypeIdentifier::Value I>
struct TypeTraits
{
    static constexpr TypeIdentifier::Value Id = I;
    static constexpr bool is(TypeIdentifier::Integer id) { return (Id == id); }
    static bool is(TypeIdentifier type_identifier) { return (Id == type_identifier.id()); }

    // And conversion functions
};


namespace TypeIdentifierDispatch {

template <typename Functor, TypeIdentifier::Value I, TypeIdentifier::Value ... Ids> struct Evaluate;

template <typename Functor>
struct Evaluate<Functor, TypeIdentifier::Unknown> {
    static typename Functor::result_type
    apply(TypeIdentifier::Integer id, const Functor&) {
        throw std::logic_error("Unknown Type");
    }
};

template <typename Functor, TypeIdentifier::Value I, TypeIdentifier::Value ... Ids>
struct Evaluate {
    static typename Functor::result_type
    apply(TypeIdentifier::Integer id, const Functor& functor) {
        if(TypeTraits<I>::is(id))
            return functor.template apply<TypeTraits<I>>();
        else return Evaluate<Functor, Ids...>::apply(id, functor);
    }
};

template <typename Functor, TypeIdentifier::Value ... Ids>
inline typename Functor::result_type
evaluate(TypeIdentifier::Integer id, const Functor& functor, TypeIdentifier::ListType<Ids...>)
{
    return Evaluate<Functor, Ids...>::apply(id, functor);
}

} // namespace TypeIdentifierDispatch

template<typename Functor>
inline
typename Functor::result_type TypeIdentifier::dispatch(const Functor& functor) {
    return TypeIdentifierDispatch::evaluate(id(), functor, TypeIdentifier::List());
}



// Usage
// =====

struct Print {
    typedef void result_type;

    template <typename Traits>
    result_type apply() const {
        std::cout << "Type Identifier: " << Traits::Id << '\n';
    }
};

inline void print_identifier(unsigned value) {
    TypeIdentifier(value).dispatch(Print());
}


int main ()
{
    print_identifier(TypeIdentifier::String);
    return 0;
}

Adding a new type to the library requires adjusting TypeIdentfier and (maybe) adding a specialized TypeTraits.

Note the enum values can be arbitrary.

0

Using recursive template you can automatically generate construction equivalent to

   if (i = A)
      Trait<A>::funct(i);
   else if (i = B)
      Trait<B>::funct(i);

I think it performance is similar to switch statement. Your original example can be rewritten as below.

#include <iostream>

using namespace std;

enum MyEnum {
   A,
   B,
   END
};

template <MyEnum N>
class Trait 
{ public:
   static int funct(int i)
   { 
      cout << "You forgot to define funct" << i;
      return i; 
   } 
};

template<>
class Trait<A> {
public:
   static int funct(int i) { return i * 3; }
};

template<>
class Trait<B> {
public:
   static int funct(int i) { return i * 24; }
};

template <MyEnum idx>
int Switch(const MyEnum p, const int n)
{
   return (p == idx) ? Trait<idx>::funct(n) : Switch<(MyEnum)(idx - 1)>(p, n);
}

template <>
int Switch<(MyEnum)(0)>(const MyEnum p, const int n)
{
   return Trait<(MyEnum)(0)>::funct(n);
}

int funct(MyEnum n)
{
   return Switch<END>(n, n);
}

int main() {
   MyEnum i = B;
   cout << funct(i);
}
Dmytro Dadyka
  • 2,208
  • 5
  • 18
  • 31