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.
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;
}
}
Approaching the age of 50 I feel free to present the vintage style solution to the problem. Traits:
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;
}
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
}
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;
}
User contributions licensed under CC BY-SA 3.0