4

I have an enum type with 1223 elements. I had a function with 1222 cases and a default case in a switch block. If I want to modify some elements in the enum type, I also need to modify that function. Worse, I may have more than one function with a big switch block. So I tried to solve it through a big array of functions, the each of which applies the right action according to the element. Because I also want to have minimal changes to do, I want the function pointer assignment done implicitly, so I use a template trick by letting the array of 1223 elements be viewed as a list of 1223 contiguous sub-arrays of 1 element to do the implicit function pointer assignment through constructors per element.

Macros are forbidden. External libraries including Boost are forbidden as well.

Here comes a simplified code (compilable and runnable if I_LAST_INSTRUCTION value is much lower) :

#include <iostream>
#include <vector>

using namespace std;

typedef size_t Instr; // dummy one for simplified code

enum
{
    I_INVALID = 0,

    I_LAST_INSTRUCTION = 1223
};

template< size_t id >
static void Test$(std::vector< Instr > &, bool)
{
    cout << "testing instruction #" << id << endl;
}

template< typename Derived, size_t start_id, size_t end_id >
struct Tester$ : Tester$ < Derived, start_id, end_id - 1 >
{
    Tester$()
    {
        static_cast<Derived *>(this)->array[end_id - 1] = Test$< end_id - 1 >;
    }
};

template< typename Derived >
struct Tester$ < Derived, 0, 0 >
{
};

struct Tester : Tester$< Tester, I_INVALID, I_LAST_INSTRUCTION >
{
    void(*array[size_t(I_LAST_INSTRUCTION)])(std::vector< Instr > & list, bool is64);

    void operator()(size_t id, std::vector< Instr > & list, bool is64)
    {
        if (id < I_LAST_INSTRUCTION)
        {
            (array[size_t(id)])(list, is64);
        }
        else
        {
            // to do nothing
        }
    }
};

int main()
{
    Tester tester;

    std::vector< Instr > list;

    tester(0, list, true); // display testing instruction #0
    tester(1, list, true); // display testing instruction #1
    tester(2, list, true); // display testing instruction #2
    tester(8, list, true); // display testing instruction #8
    tester(1222, list, true); // display testing instruction #1222
    tester(1223, list, true); // invalid instruction number - do nothing
}

Because I_LAST_INSTRUCTION is too high, I got this error with VC2013:

fatal error C1202: recursive type or function dependency context too complex

The compiler appears to accept no more than 499 nested class template instantiations.

The solution I can see is to define the nested class template instantiations as a binary tree so its max depth is close to log2(n) instead of a list (its max depth being n).

So my question is how to implement that meta-list into a meta-binary-tree efficiently in order to make the compiler happy?

Edit: another solution can be to use a list with more elements per sub-array to divide the depth list by the max number of element in a sub-array. Using 4 elements per sub-array solve the issue I had.

Edit 2: more details about why I chose this way

My instructions are described through template classes composition:

namespace x86
{
    namespace encoder
    {
        // Group 8086+
        template<> struct Opcode$< I_AAA > : Opcode < I_AAA, 0x00000037, Gw < RW >, DummyRw< 0, AX >, i64 > {};

        template<> struct Opcode$< I_AAD > : Opcode < I_AAD, 0x000000D5, Gw_Ib < RW >, DummyRw< 0, AX >, i64 > {};

        template<> struct Opcode$< I_AAM > : Opcode < I_AAM, 0x000000D4, Gw_Ib < RW >, DummyRw< 0, AX >, i64 > {};

        template<> struct Opcode$< I_AAS > : Opcode < I_AAS, 0x0000003F, Gw < RW >, DummyRw< 0, AX >, i64 > {};

        template<> struct Opcode$< I_ADC > :
            Switch
            <
            /**/ Opcode < I_ADC, 0x00000012, Gb_Eb  < RW, R >, OSb             >,
            /**/ Opcode < I_ADC, 0x00000013, Gw_Ew  < RW, R >, OSw             >,
            /**/ Opcode < I_ADC, 0x00000013, Gd_Ed  < RW, R >, OSd             >,
            /**/ Opcode < I_ADC, 0x00000013, Gq_Eq  < RW, R >, OSq             >,
            /**/ Opcode < I_ADC, 0x00000010, Eb_Gb  < RW, R >, OSb             >,
            /**/ Opcode < I_ADC, 0x00000011, Ew_Gw  < RW, R >, OSw             >,
            /**/ Opcode < I_ADC, 0x00000011, Ed_Gd  < RW, R >, OSd             >,
            /**/ Opcode < I_ADC, 0x00000011, Eq_Gq  < RW, R >, OSq             >,
            /**/ Opcode < I_ADC, 0x00000014, AL_Ib  < RW    >                  >,
            /**/ Opcode < I_ADC, 0x00000080, Eb_Ib  < RW    >, OSb, Group1 <2> >,
            /**/ Opcode < I_ADC, 0x00000083, Ew_Ib  < RW    >, OSw, Group1 <2> >,
            /**/ Opcode < I_ADC, 0x00000083, Ed_Ib  < RW    >, OSd, Group1 <2> >,
            /**/ Opcode < I_ADC, 0x00000083, Eq_Ib  < RW    >, OSq, Group1 <2> >,
            /**/ Opcode < I_ADC, 0x00000015, AX_Iw  < RW    >, OSw             >,
            /**/ Opcode < I_ADC, 0x00000015, EAX_Id < RW    >, OSd             >,
            /**/ Opcode < I_ADC, 0x00000015, RAX_Id < RW    >, OSq             >,
            /**/ Opcode < I_ADC, 0x00000081, Ew_Iw  < RW    >, OSw, Group1 <2> >,
            /**/ Opcode < I_ADC, 0x00000081, Ed_Id  < RW    >, OSd, Group1 <2> >,
            /**/ Opcode < I_ADC, 0x00000081, Eq_Id  < RW    >, OSq, Group1 <2> >
            > {};
    ...

template arguments after the second in Opcode are used for both instruction operands matching for a valid Instr(id, opd1, opd2, ...) and instruction encoding when an Opcode description matches.

I had a big switch block which is a big pain:

void Backend::EncodeInstr(Instr & instr)
{
    switch (instr.id_)
    {
    case I_AAA:                 JITASM_ASSERT(encoder::Opcode$< I_AAA >::Encode(instr, is64_)); break;
    case I_AAD:                 JITASM_ASSERT(encoder::Opcode$< I_AAD >::Encode(instr, is64_)); break;
    case I_AAM:                 JITASM_ASSERT(encoder::Opcode$< I_AAM >::Encode(instr, is64_)); break;
    case I_AAS:                 JITASM_ASSERT(encoder::Opcode$< I_AAS >::Encode(instr, is64_)); break;
    case I_ADC:                 JITASM_ASSERT(encoder::Opcode$< I_ADC >::Encode(instr, is64_)); break;
    ...

And the same for Testinstr (its purpose is to generate a list of instructions matching all opcodes to check the encoder is correct). For instance, TestInstr(I_XOR) will give:

0x10000000( 2):                 DA32 xor              bl, dl
0x10000002( 6):         555555551D32 xor              bl, byte ptr [0x55555555]
0x10000008( 3):               DA3366 xor              bx, dx
0x1000000B( 7):       555555551D3366 xor              bx, word ptr [0x55555555]
0x10000012( 2):                 DA33 xor              ebx, edx
0x10000014( 6):         555555551D33 xor              ebx, dword ptr [0x55555555]
0x1000001A( 2):                 DA32 xor              bl, dl
0x1000001C( 6):         555555551530 xor              byte ptr [0x55555555], dl
0x10000022( 3):               DA3366 xor              bx, dx
0x10000025( 7):       55555555153166 xor              word ptr [0x55555555], dx
0x1000002C( 6):         555555551531 xor              dword ptr [0x55555555], edx
0x10000032( 2):                 5534 xor              al, 0x55
0x10000034( 3):               55F380 xor              bl, 0x55
0x10000037( 7):       55555555553580 xor              byte ptr [0x55555555], 0x55
0x1000003E( 4):             55F38366 xor              bx, 0x55
0x10000042( 8):     5555555555358366 xor              word ptr [0x55555555], 0x55
0x1000004A( 3):               55F383 xor              ebx, 0x55
0x1000004D( 7):       55555555553583 xor              dword ptr [0x55555555], 0x55
0x10000054( 4):             55553566 xor              ax, 0x5555
0x10000058( 5):           5555555535 xor              eax, 0x55555555
0x1000005D( 5):           5555F38166 xor              bx, 0x5555
0x10000062( 9):   555555555555358166 xor              word ptr [0x55555555], 0x5555
0x1000006B( 6):         55555555F381 xor              ebx, 0x55555555
0x10000071(10): 55555555555555553581 xor              dword ptr [0x55555555], 0x55555555

So I only need to define the enum type of intruction id and define the matching opcodes for each instruction id. Everything else is done under the hood, except for the two big switch blocks in both EncodeInstr and TestInstr I had to explicit.

hlide
  • 237
  • 2
  • 10
  • 1
    How about... ``#include #include typedef std::map > DispatcherMap_t;`` And then add all your test functions into that map? Then you can write `` myDispatcherMap [enumId]()`` or something like that. – BitTickler Jan 17 '15 at 22:25
  • Could you explain a bit more, what those changes are you try to cover? You add/remove/rename entries in the enum and the array of functions you have...shall still have a 1:1 relationship to the old enum entries? And those enum entries are automatically assigned values or manually assigned values? – BitTickler Jan 17 '15 at 22:41
  • 1) Performance: function array with O(1) look-up. 2) Automatically assigned function pointer per entry : see constructor of Tester$< start_id, end_id >, it does it. 3) only need to define/undeclare Test$< id > for any changes of an enumerated value. 4) I use a template list-like definition for Tester$ to create this fast array with no explicit function pointer initializiation. I want to transform it into a template binary-tree-like definition. – hlide Jan 17 '15 at 22:45
  • 1223 members of an `enum` is _excessive_. You should really rethink what you're trying to do and try to eliminate the vast majority of those values. `$` is _not_ a standard character for identifiers, don't use it. – Captain Obvlious Jan 17 '15 at 23:42
  • 1
    @CaptainObvlious Blame x86 architecture. That is the result of the number of mnemonics with x86 instructions up to AVX-512 and including special instructions. As for $, I just use it here for template classes or functions "highlighting". – hlide Jan 17 '15 at 23:56

4 Answers4

5

Use a table of entities.

You could also use std::map<value, function_pointer> which may be faster depending on your enumeration values.

The table is also called a jump table. Many compilers will convert switch statements into jump tables. Although the table of may be what the compiler generates, I believe the table is easier to maintain then a switch statement for large quantities of cases.

Edit 1: - Example
Simple Version: array of function pointers.

// Synonym for pointer to a function that has no parameters
//    and returns no values.
typedef void (*Function_Pointer)(void);

// Prototypes
void Eat(void);
void Sleep(void);
void Drink(void);

// The table
const static Function_Ptr dispatch_table[] =
{ /* Index 0 */ Eat,
  /* Index 1 */ Sleep,
  /* Index 2 */ Drink,
};

// Execution syntax
unsigned int index = 1;
(dispatch_table[index])();  // Execute Sleep() function.

More Robust Version: Associating enum with function pointer.

struct Dispatch_Entry
{
  unsigned int  function_ID;
  Function_Pointer p_function;
};

const static Dispatch_Entry robust_dispatch_table[] =
{
  // Unlike the array, this structure allows the
  // function pointers to be listed in any order.
  // Also, they don't have to be contiguous.
  {2, Drink},
  {0, Eat},
  {1, Sleep},
};
const unsigned int num_entries =
  sizeof(robust_dispatch_table) / sizeof(robust_dispatch_table[0]);

// Look up the function:
for (unsigned int i = 0;
     i < num_entries;
     ++i)
{
  if (robust_dispatch_table[i].function_ID == function_id_to_execute)
  {
    (robust_dispatch_table[i].p_function)(); // Execute function.
    break;
  }
}
Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
  • It won't. An array look-up is always O(1) (switch block was turned into an internal array of function pointers by the compiler). Moreover, a map initialization would need to set its values explicitly, which has the same annoyance as the switch block. – hlide Jan 17 '15 at 22:26
  • A draw back is that all the functions must have the same signature per table. You may be able to get around this limitation by using a pointer to a Function Object base class. – Thomas Matthews Jan 17 '15 at 22:29
  • if you have the functions in an array already, you can create the map in a loop. Last I checked, std::map were red black trees - lookup should be fast enough for 99% of the applications. – BitTickler Jan 17 '15 at 22:30
  • I need a binary tree constructed at compiled-time by the compiler through a clever use of template, not a dynamic map. Such a dynamic map is not acceptable for instructions analyzer, interpreter or compiler in performance term. – hlide Jan 17 '15 at 22:36
  • Use a static table. A static, const, table can be considered as data and passes static analyzers and is accessed directly. A sorted table can use a *binary search* for huge amounts; however there may not be a big improvement over a linear search because of the overhead (since your table isn't huge). I use these all the time in performance critical embedded systems, especially with message dispatching and GUI menus. – Thomas Matthews Jan 17 '15 at 22:46
  • The code I posted is a simplified one. The real _enum_ type contains more than 1223 values. Since I can change the order when adding an instruction, I want to minimize the change elsewhere. I have two functions using a switch block : one is encoding an x86 instruction into bytes, the other is creating a list of instructions per id for testing purpose. The solution I had would be totally perfect if there were no limitation about nested class template instantiation. What I want is a similar solution overcoming the limitation by reducing it to a number close to log2(n) instead of n. – hlide Jan 17 '15 at 23:14
  • 3
    @hlide It seems you are very specific in your requirements, yet you keep coming up with "extraneous" reasons (i.e. not in the question) why only the thing you came up with can work. What do you want? Our blessing? Well then: _Permission granted to be your own architect. May the force be with you_. – sehe Jan 17 '15 at 23:25
  • simply what is already said in my primary post : can someone give me some hints how to transform that list-like template definition (N nested Tester$ classes in Test class - array of N elements being viewed as a list of N contiguous sub-arrays of 1 element) into a tree-like template definition (that is, an array of N elements viewed as an infix binary tree of sub-arrays of power of two elements according to their depth in the tree) – hlide Jan 17 '15 at 23:44
  • Maybe the static binary tree is not the only solution. I can try a list of N/I sub-arrays of I elements instead of a list of N sub-arrays of 1 element to reduce the list length by I, which is probably simpler to implement. – hlide Jan 18 '15 at 00:07
0

Approaching the age of 50 I feel free to present the vintage style solution to the problem. Traits:

  1. O(1) lookup.
  2. Table is not constructed/sorted at compile time, but instead at pre-main init time. A constructor writing 1200 function pointer values to a static array should not take more than a few microseconds.
  3. Template free - even the most silly of C++ compiler can handle this. Also, probably without all those templates your image size will be so much smaller that the runtime initialization of the array is amortized by the loading time for the image.
  4. Suitable to produce heart attacks to all your C++11++-whatnot fans.
  5. Took me only 30min...
  6. Most of the macro stuff you can throw into a header file (where your enum probably is...)

Here it is:

#include <iostream>
#include <vector>

typedef size_t Instr;

typedef enum Foo
{
    I_INVALID = 0
,   F_1 // for lack of better names... could as well be foo bar baz...
,   F_2
,   I_LAST_INSTRUCTION = 20
} Foo_t;

typedef void(*TestFunction_t)(std::vector<Instr> &, bool);

#define BEGIN_TEST_FUNCTION(id,name) \
    static void name (std::vector<Instr> & l, bool b) \
        { \
        static const Foo_t s_id = id; 
#define END_TEST_FUNCTION()\
        }

#define DECLARE_TEST_FUNCTION(id,name) \
    s_functions[id] = name;

#define INVOKE(id) \
    if( NULL != Invoker::s_functions[id]) \
        Invoker::s_functions[id]

BEGIN_TEST_FUNCTION(F_1,Test1)
    std::cout << "testing instruction #" << s_id << std::endl;
END_TEST_FUNCTION()

BEGIN_TEST_FUNCTION(F_2, Test2)
std::cout << "testing instruction #" << s_id << std::endl;
END_TEST_FUNCTION()

class Invoker
{
public:
    static TestFunction_t s_functions[I_LAST_INSTRUCTION];
    Invoker()
    {
        DECLARE_TEST_FUNCTION(F_1, Test1)
        DECLARE_TEST_FUNCTION(F_2, Test2)
        // ... and so on...
    }
};
static Invoker s_invokerInitializer;

TestFunction_t
Invoker::s_functions[I_LAST_INSTRUCTION] =
{
    {0}
};


int main(int argc, const char *argv[])
{
    std::vector<Instr> whatever;
    INVOKE(F_1)(whatever, true);
    INVOKE(F_2)(whatever, true);
    return 0;
}
BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • You could also define 2 macros which wrap the class Invoker... code. Like BEGIN_TEST_FUNCTION_TABLE() ... END_TEST_FUNCTION_TABLE(). Then it would look even less like a hack. – BitTickler Jan 18 '15 at 00:23
  • 1
    Take no offense please. My project is C++11 and I still prefer to debug template classes and functions than macros. I know very well what I don't want so it is why I am using that template trick to make my future changes easier (it is experimental and a lot of features may be added) But anyway, I think the issue here is I'm asking a very precise question that probably nobody is understanding or does not understand the circumstance why I'm doing that. – hlide Jan 18 '15 at 00:47
  • Do you really want to end up with code like this... ? http://stackoverflow.com/questions/7188182/quick-sort-at-compilation-time-using-c11-variadic-templates And then pray each time if your compiler will do the job? And end up with larger image and longer compile times? Just for the sake of using something "modern"? – BitTickler Jan 18 '15 at 00:53
  • 1
    Surely not, my first objective is to let compiler do all the boilerplate stuff instead of me. As long as it makes the real hard stuff readable and maintainable - especially during work in progress, I am satisfied with template tricks. I am not satisfied with your solution because it does not scope very well with the real stuff I did not post. Sure, your concerns about image size and compilation slowness are valid. But it is up to me to choose the best balance between template and non-template solutions. – hlide Jan 18 '15 at 13:29
0

Ok, if I choose an array of N elements viewed as a list of N/4 sub-arrays of 4 elements instead of the previous list of N sub-arrays of 1 element, compilation and execution are successful with right results (306 nests which is under 499 limit in VC2013). I wish it can be doable as a binary-tree so the nest count will be logarithmic (11 nests instead of 1223 nests!).

#include <iostream>
#include <vector>

using namespace std;

typedef size_t Instr; // dummy one for simplified code

enum
{
    I_INVALID = 0,

    I_LAST_INSTRUCTION = 1223
};

template< size_t id >
static void Test_T(std::vector< Instr > &, bool)
{
    cout << "testing instruction #" << id << endl;
}

template< typename Derived, size_t start_id, size_t end_id, size_t rem = ((end_id - start_id) & 3) >
struct Tester_T;

template< typename Derived, size_t start_id, size_t end_id >
struct Tester_T < Derived, start_id, end_id, 0 > : Tester_T < Derived, start_id, end_id - 4 >
{
    Tester_T()
    {
        static_cast<Derived *>(this)->array[end_id - 4] = Test_T< end_id - 4 >;
        static_cast<Derived *>(this)->array[end_id - 3] = Test_T< end_id - 3 >;
        static_cast<Derived *>(this)->array[end_id - 2] = Test_T< end_id - 2 >;
        static_cast<Derived *>(this)->array[end_id - 1] = Test_T< end_id - 1 >;
    }
};

template< typename Derived, size_t start_id, size_t end_id >
struct Tester_T < Derived, start_id, end_id, 1 > : Tester_T < Derived, start_id, end_id - 3 >
{
    Tester_T()
    {
        static_cast<Derived *>(this)->array[end_id - 3] = Test_T< end_id - 3 >;
        static_cast<Derived *>(this)->array[end_id - 2] = Test_T< end_id - 2 >;
        static_cast<Derived *>(this)->array[end_id - 1] = Test_T< end_id - 1 >;
    }
};

template< typename Derived, size_t start_id, size_t end_id >
struct Tester_T < Derived, start_id, end_id, 2 > : Tester_T < Derived, start_id, end_id - 2 >
{
    Tester_T()
    {
        static_cast<Derived *>(this)->array[end_id - 2] = Test_T< end_id - 2 >;
        static_cast<Derived *>(this)->array[end_id - 1] = Test_T< end_id - 1 >;
    }
};

template< typename Derived, size_t start_id, size_t end_id >
struct Tester_T < Derived, start_id, end_id, 3 > : Tester_T < Derived, start_id, end_id - 1 >
{
    Tester_T()
    {
        static_cast<Derived *>(this)->array[end_id - 1] = Test_T< end_id - 1 >;
    }
};

template< typename Derived >
struct Tester_T < Derived, 0, 0 >
{
};

struct Tester : Tester_T< Tester, I_INVALID, I_LAST_INSTRUCTION >
{
    void(*array[size_t(I_LAST_INSTRUCTION)])(std::vector< Instr > & list, bool is64);

    void operator()(size_t id, std::vector< Instr > & list, bool is64) const
    {
        if (id < I_LAST_INSTRUCTION)
        {
            (array[size_t(id)])(list, is64);
        }
        else
        {
            // to do nothing
        }
    }
};

static Tester const tester; 

int main()
{   
    std::vector< Instr > list;

    tester(0, list, true); // display testing instruction #0
    tester(1, list, true); // display testing instruction #1
    tester(2, list, true); // display testing instruction #2
    tester(3, list, true); // display testing instruction #3
    tester(4, list, true); // display testing instruction #4
    tester(8, list, true); // display testing instruction #8
    tester(15, list, true); // display testing instruction #15
    tester(16, list, true); // display testing instruction #16
    tester(1024, list, true); // display testing instruction #1024
    tester(1222, list, true); // display testing instruction #1222
    tester(1223, list, true); // invalid instruction number - do nothing
    tester(2048, list, true); // invalid instruction number - do nothing
}

Edit: the following code is doing the same in a more generic way (you can set the number max of element in a sub-array by setting the number of bits to encode an index of an element in a sub-array as the fourth template parameter of class Test_T)

#include <iostream>
#include <vector>

using namespace std;

typedef size_t Instr; // dummy one for simplified code

enum
{
    I_INVALID = 0,

    I_LAST_INSTRUCTION = 1223
};

template< size_t id >
static void Test_T(std::vector< Instr > &, bool)
{
    cout << "testing instruction #" << id << endl;
}

struct Tester;

template< size_t start_id, size_t end_id >
struct TestArrayInitializer_T
{
    static void Set(Tester & tester)
    {
        tester.array[start_id] = Test_T < start_id > ;
        TestArrayInitializer_T< start_id + 1, end_id >::Set(tester);
    }
};

template< size_t start_id >
struct TestArrayInitializer_T < start_id, start_id >
{
    static void Set(Tester & tester)
    {
        tester.array[start_id] = Test_T < start_id > ;
    }
};

template< typename Derived,
          size_t   start_id,
          size_t   end_id,
          size_t   bits,
          size_t   N = (1 << bits),
          size_t   i = (end_id - start_id) & (N - 1) >
struct Tester_T : Tester_T < Derived, start_id, end_id - N + i, bits >
{
    Tester_T()
    {
        TestArrayInitializer_T< end_id - N + i, end_id - 1 >::Set(*static_cast<Derived *>(this));
    }
};

template< typename Derived, size_t bits, size_t N, size_t i >
struct Tester_T < Derived, 0, 0, bits, N, i >
{
};

struct Tester :
    Tester_T < Tester,
               I_INVALID,
               I_LAST_INSTRUCTION,
               8 /* 256 elements per sub-array */ >
{
    void(*array[size_t(I_LAST_INSTRUCTION)])(std::vector< Instr > & list, bool is64);

    void operator()(size_t id, std::vector< Instr > & list, bool is64) const
    {
        if (id < I_LAST_INSTRUCTION)
        {
            (array[size_t(id)])(list, is64);
        }
        else
        {
            // to do nothing
        }
    }
};

static Tester const tester;

int main()
{
    std::vector< Instr > list;

    tester(0, list, true); // display testing instruction #0
    tester(1, list, true); // display testing instruction #1
    tester(2, list, true); // display testing instruction #2
    tester(3, list, true); // display testing instruction #3
    tester(4, list, true); // display testing instruction #4
    tester(8, list, true); // display testing instruction #8
    tester(15, list, true); // display testing instruction #15
    tester(16, list, true); // display testing instruction #16
    tester(1024, list, true); // display testing instruction #1024
    tester(1222, list, true); // display testing instruction #1222
    tester(1223, list, true); // invalid instruction number - do nothing
    tester(2048, list, true); // invalid instruction number - do nothing
}
hlide
  • 237
  • 2
  • 10
  • added a more generic way to choose the max number of elements in a sub-array without the need to add/remove template specializations as it would be the case with the first source to grow/shrink the max number of elements. – hlide Jan 18 '15 at 04:04
0

Just for the sake of completeness. Here a version which does not use macros, but does not use templates either.

Again O(1) and again fast compile times and small image size. And again, one time run time initialization in the range of microseconds.

Sorry if I misunderstood the question. To me it is about how to deal with the association between the large enum and test functions, just as the title says. To me it is not an exercise in meta programming.

To get rid of the throw, of course it is always possible to remove operator() and add a function taking the enum id as an additional parameter.

#include <iostream>
#include <vector>

typedef size_t Instr;

typedef enum Foo
{
    I_INVALID = 0
,   F_1 // for lack of better names... could as well be foo bar baz...
,   F_2
,   I_LAST_INSTRUCTION = 20
} Foo_t;

typedef void(*TestFunction_t)(std::vector<Instr> &, bool);

void Test1(std::vector<Instr>& l, bool b)
{
    const Foo_t ID = F_1;

    std::cout << "testing instruction #" << ID << std::endl;
}


void Test2(std::vector<Instr> & l, bool b)
{
    const Foo_t ID = F_2;

    std::cout << "testing instruction #" << ID << std::endl;
}


class Associator
{
public:
    static TestFunction_t s_functions[I_LAST_INSTRUCTION];
    static bool s_initialized;

    static inline void associate(size_t id, TestFunction_t fun)
    {
        s_functions[id] = fun;
    }

    Associator()
    {   
        // Only first instance runs the code below..
        // Does not look as if but it IS thread safe.
        // Why? Even if multiple threads race here, 
        // they all write the same values.
        if ( !s_initialized )
        {  
            s_initialized = true;

            // Add one line per test function.
            // Cannot see how to do without associating
            // the enum value with a function.
            associate(F_1, Test1);
            associate(F_2, Test2);
        }
    }

    TestFunction_t operator() (size_t id)
    {
        if (NULL == s_functions[id])
            throw std::invalid_argument("No function for this id.");
        return s_functions[id];
    }
};

TestFunction_t Associator::s_functions[I_LAST_INSTRUCTION] =
{
    0
};
bool Associator::s_initialized = false;


int main(int argc, const char * argv)
{
    Associator assoc; 

    std::vector<Instr> whatever;
    assoc(F_1)(whatever, true);
    assoc(F_2)(whatever, true);

    return 0;
}
BitTickler
  • 10,905
  • 5
  • 32
  • 53
  • I would think that the metaprogramming aspect comes in at the initialisation – you don’t want to write 1223 `associate` statements by hand, and you *certainly* don’t want to maintain such a code. Whether this is then solved via macros or templates would be – for me at least – much less consequential. – Konrad Rudolph Jan 18 '15 at 14:02
  • It depends on your use case, really. If you want to make sure, that whenever you use enum value FOO, function X is executed, no matter how you move FOO around in your enum or how you give it a new value, then there is no other choice but to associate the function explicitely with the enum value. Nothing else can work. If on the other hand you do not care about which enum value maps to which functions, you would better be adviced to delete your enum as it is obviously of no semantic value. – BitTickler Jan 18 '15 at 14:23