1

I have the following code in C++ (C++11 is available)

typedef enum
{ 
   APPLE,
   ORANGE,
   LAST,
} fruits_t;

template <typename T, size_t size> char (&ArraySizeHelper( T (&)[size]))[size];
#define arraysize(pArray) sizeof(ArraySizeHelper(pArray))

static const char *LOOKUP_TABLE[]
{
   "apple",  // APPLE
   "orange", // ORANGE
};
// Fail the compilation if the lookup table is missing anything
static_assert(arraysize(LOOKUP_TABLE) == LAST, "Update the lookup table");

I want to be able to change the order in the enumeration without changing the initialisation list in the LOOKUP_TABLE.

I have read this Initialization of a normal array with one default value and the template is almost what I need, but not quite. I struggled with metaprogramming for half a day and still can not figure out how to do it.

The actual enumeration in the code starts from zero and contains 20-30 entries. I have considered a hash table - the hash function is trivial, but the problem of initialization remains. I have many - 5+ - lookup tables like this in the code. Is it worth the struggle?

P.S. After a couple of answers around switch construct let me show a chunk of the real code. This is just one example.

typedef enum
{
    EVENTS_STATISTICS_COLLECTOR_POOL_TIMEOUT            ,
    EVENTS_STATISTICS_COLLECTOR_POOL_FAILED             ,
    EVENTS_STATISTICS_COLLECTOR_SENT_TO_PIPELINE        ,

    // 50 entries like this 
    EVENTS_STATISITCS_LAST                              ,
} events_statistics_t;

uint64_t events_statistics[EVENTS_STATISITCS_LAST];
const char *event_statistics_names[] = {
    "collector_pool_timeout         ",
    "collector_pool_failed          ",
    "collector_sent_to_pipeline     ",
    // and so on ...
};

static inline void events_statistics_bump_counter(events_statistics_t counter)
{
    events_statistics[counter]++;
}

// The actual function is a generic one which prints arbitrary pairs
// But this one gives an idea
void print_statistics()
{
    int col = 0;
    static const int COLUMNS = 3;
    printf("\n");
    for (int i = 0;i < EVENTS_STATISITCS_LAST;i++)
    {
        printf("%-30s %9lu", event_statistics_names[i], events_statistics[i]);
        col++;
        if ((col % COLUMNS) == 0)
            printf("\n");
        else
            printf("%4s", "");
    }
    if ((col % COLUMNS) != 0)
        printf("\n");
}

P.S.2 What I wanted to say in the previous P.S. is that "switch" gives too much freedom to the compiler. I want to ensure that the lookup is a simple table and not some double indexed array or if/else branches

P.S.3 Here is another example of an initialised array. The array contains hooks and it is of an extreme importance to call the correct function.

typedef struct
{
    const int id;
    const events_process_t processor;
    uint64_t counter;
} events_process_table_t;

static events_process_table_t events_processors[EVENT_ID_LAST] =
{
        {EVENT_ID_OPEN          , (events_process_t)events_process_open             },
        {EVENT_ID_OPENAT        , (events_process_t)events_process_open             },
        // and so on for 30 lines
};
static_assert(arraysize(events_processors) == EVENT_ID_LAST, "Update the table of events processors");

void some_code(int event_id)
{
        events_process_table_t *table = &events_processors[event_id];
        if (event_id == table->id)
        {
              // Looks alright and I can use the table->processor
              // ..... 
        }
}
Community
  • 1
  • 1
Larytet
  • 648
  • 3
  • 13
  • @YuHao template programming and/or preprocessor macros is apparently the way to solve the problem, but how exactly. C++ does not allow initialisation of arrays like C99 {[APPLE]:"apple", [ORANGE]:"orange"} – Larytet Feb 03 '16 at 03:11
  • What use do you make of the enum and the string representation of its values? – Shoe Feb 03 '16 at 03:20
  • @shoe In the real code the data in the lookup table is a non-trivial object (memory pool). The code parses incoming events, depending on the event identifier chooses the memory pool and allocates an object from the pool. The enumeration is events identifiers. In yet another table the strings are descriptions of the debug counters (~50 of counters). The debug counters is part of the array of debug counters. I have a print function which loops over the array and prints pairs value:"description". – Larytet Feb 03 '16 at 03:27
  • @larytet - Doesn't and item in the memory pool just boil down to an address though? – Kingsley Feb 03 '16 at 04:00

2 Answers2

2

I think that the most efficient way to handle this situation is with a function that maps the enum values to their corresponding C-string:

enum class fruits { apple, orange, last };

char const* show(fruits x) {
    switch (x) {
        case fruits::apple: return "apple";
        case fruits::orange: return "orange";
        case fruits::last: return "last";
    }
    assert(false);
    return nullptr;
}

Live demo

At this point, if you forget to define fruits::last's string in show, most compilers will warn you with something like:

main.cpp: In function 'const char* show(fruits)':

main.cpp:7:12: warning: enumeration value 'last' not handled in switch [-Wswitch]

 switch (x) {

Live demo

which if you compile with -Werror will prevent the compilation.

Shoe
  • 74,840
  • 36
  • 166
  • 272
  • I would like to stick to the lookup table. Use of switch can end in the less optimised code and depends on the actual enumeration. – Larytet Feb 03 '16 at 03:17
0

Given the crux of your question is:

I want to be able to change the order in the enumeration without changing the initialisation list in the LOOKUP_TABLE.

This is a very simple problem:

const char *fruityLookup(fruits_t f)
{
    switch (f)
    {
        case APPLE:  return "apple";
        case ORANGE: return "orange";
        case DURIAN: return "Woah!";
        // do not add a default
    }
    assert(false); // check definition of fruits_t
}

Yes, you can't do a static_assert() on it. But you don't need to. A lot of modern compilers will even warn you when one of enum fruits_t is not handled in the switch().

For 30 or so values the function will get a little long-winded, but the list would have to be quite long before a hash table would start to out-pace a switch().

This is more readable than a templated function involving macros. If you need it for a uni' assignment or whatever, then fine. But the above works in both C and C++, and doesn't need C++11.

Just trying to keep it simple.

Kingsley
  • 14,398
  • 5
  • 31
  • 53
  • I've posted the same exact solution more than 15 minutes ago. What does this answer add? Not to mention that with a default clause in the `switch` statement, the compiler [will probably not](http://coliru.stacked-crooked.com/a/d08ffd9a5e7d9587) warn about a missing enum value. – Shoe Feb 03 '16 at 03:33
  • @shoe - Yes, but when I started typing there was no answer. Good point on the default though. – Kingsley Feb 03 '16 at 03:38
  • @kingsley your answer did not deserve -1 I fixed the problem. Between an array and switch I will keep the array. Performance is more important and the array looks better. I would really like to handle holes in the enumeration without performance penalty - a hash table. – Larytet Feb 03 '16 at 05:20
  • There are still issues with this answer. Few of which can be fixed by trying to compile the code. – Shoe Feb 03 '16 at 11:15