Something that acts like a lookup table can be provided pretty
straightforwardly using a function-like macro. Here is a simple
sketch (I chose different numbers for the example values):
#define table(k) ( \
(k) == 0 ? 27 : \
(k) == 1 ? 28 : \
(k) == 2 ? 29 : \
(k) == 3 ? 35 : \
(k) == 4 ? 47 : \
-1 \
)
(Obviously we might want to choose a better name in actual code.)
Calls to the table() macro are usable as constant expressions
when the "index" is a constant expression:
enum { SOMETHING = 3 };
char foo[ table(SOMETHING) ];
char bas[ table((int)2.0) ];
char bar[ table(12-11) ];
const int fantastic = 4;
char foobas[ table(fantastic) ];
Calls to table() with a constant argument also can be used as
'case' labels, if that is wanted.
If dynamic lookup is needed in addition to compile-time lookup,
this can be done by providing an inline function that accesses an
array:
extern const int table_values_array[];
static inline int
(table)( int index ){
return table_values_array[ index ];
}
Any "indexing" where the index is a dynamic value (eg, an auto
variable) rather than a compile-time constant may use the 'table'
function rather than the macro (this is done in the usual way by
putting parentheses around the function name):
int
dumb_example( int foo, int bas ){
return (table)(foo-bas);
}
Even low levels of optimization (eg, -O1 in gcc) should turn this
function call into just an array access.
The initializers for elements of table_values_array[] may be
written using repeated calls to the table() macro, to ensure
synchronization:
const int table_values_array[] = {
table(0),
table(1),
table(2),
table(3),
table(4),
};
That's everything. Not very high-powered, and some people may
blanch at using the preprocessor, but it works and seems easy
enough to understand.
The method shown above is simple, but it does have a problem of
sorts - the number of calls to table() in the array initializer
need to be kept in sync with the macro definition "by hand", as
it were. There are various techniques for avoiding this problem.
Here is one method, showing first how to define the values and
the table() macro:
#define TABLE_ENTRIES_FOREACH(k,WHAT) \
WHAT(k,0,27) \
WHAT(k,1,28) \
WHAT(k,2,29) \
WHAT(k,3,35) \
WHAT(k,4,47) \
/* end of TABLE_ENTRIES_FOREACH */
#define table(k) ( TABLE_ENTRIES_FOREACH( k, TABLE_VALUE_CHOOSE ) -1 )
#define TABLE_VALUE_CHOOSE( k, key, value ) (k) == (key) ? (value) :
And now the definition of the values array, with initializers:
const int table_values_array[] = {
#define TABLE_ARRAY_INITIALIZER( _1, _2, value ) (value),
TABLE_ENTRIES_FOREACH( 0, TABLE_ARRAY_INITIALIZER )
#undef TABLE_ARRAY_INITIALIZER
};
Obviously this scheme is more complicated in terms of how much
preprocessor machinery is used. Its compensating advantage is
that now all the values and indices are in only one place.
Different circumstances may favor one or the other, depending
on a variety of forces.