Yet another static array initialization in C++

时间:2016-02-03 02:59:55

标签: c++ arrays static initialization

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
              // ..... 
        }
}

2 个答案:

答案 0 :(得分: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.

答案 1 :(得分: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.