9

I have some constant values and arrays defining their labels and their hash codes. For example,

#define LABEL_A 0 //or const int LABEL_A = 0;
#define LABEL_B 1
#define LABEL_C 2
#define LABEL_D 3

const char *VALUE[] = {"LABEL_A", "LABEL_B", "LABEL_C", "LABEL_D"};
const int VALUE_HASH[] = {67490, 67491, 67493, 67459);

At run-time, these labels can come in any order and needs to be parsed accordingly. I am using switch case for this purpose. This code is generating error at compile time "constant expression is required.

function(const char* LabelAtRuntime){
  int i = getHashCode(LabelAtRuntime);
  switch(i){
    case VALUE_HASH[LABEL_A]: //line giving compile time error
      break;
    default:
      break;
}

But, when I provide actual constants, it works. This code works well.

function(const char* LabelAtRuntime){
  int i = getHashCode(LabelAtRuntime);
  switch(i){
    case 67490: //line not giving compile time error
      break;
    default:
      break;
}
  1. I am unable to understand, why is it happening? Both my array and its index are constants, then isn't it equivalent to a constant literal?
  2. Is there any other way by which I can provide my constants in required manner?

I am using constants in this manner to provide better code semantics, readability and reusability. Please do not provide if-else based solution. In above example, there are only 4 labels, but in practical, there could be 100.

Amber Beriwal
  • 1,568
  • 16
  • 30
  • Indexing into arrays is actually dereferencing a pointer. I believe this cannot be done statically by the compiler and therefore the error. – scorpGoku Mar 06 '17 at 09:44
  • `case 67490: //line giving compile time error`....err..really? – Sourav Ghosh Mar 06 '17 at 09:46
  • 1
    How about `#define LABEL_A_HASH 67490`? – JimmyB Mar 06 '17 at 09:49
  • 9
    Pick either C or C++. Your code with `const int LABEL_A` will not compile in C but can be made to compile in later versions of C++. So it is not meaningful to give a "C/C++" answer for this, please remove either language from the question. – Lundin Mar 06 '17 at 09:59
  • 2
    Have you considered creating an array of pointers to functions and simply call the needed function without if-else statements or switch-cases? – Shay Gold Mar 06 '17 at 10:02
  • -Sourav, Typo resolved. -JimmyB, I do not want to define separate values. It will end the purpose of defining labels as constants. -Lundin, I am searching for answer compatible with C. If not found anything, then I can think of moving on to C++. -Shay, How would this work? – Amber Beriwal Mar 06 '17 at 10:16
  • Compute the hash value with pre-processor math. What is the hash function? – chux - Reinstate Monica Mar 06 '17 at 14:57
  • Note that VALUE is not actually const. – user253751 Mar 06 '17 at 20:53
  • @chux it is the same hash function that java.lang.String uses. – Amber Beriwal Mar 07 '17 at 04:35

6 Answers6

11

In C++, this compiles:

#include <stdio.h>
#include <stdlib.h>

constexpr int x[] = { 42, 43 };

int main(int argc, char **argv)
{
    switch(atoi(argv[1]))
    {
        case x[0]: puts("forty_two");
                   break;
        case x[1]: puts("forty_three");
    }
    return 0;
}

So constexpr on the array appears to be the solution in modern C++. (Note: the question was originally tagged C++ and C)

It's impossible in C if you want to keep the array. Switch cases require an integer constant, but once you put an integer constant in a variable, it becomes a runtime entity (even if it's declared const). What you could do is replace the in-memory array with just a bunch of direct defines and possibly have a macro that looks up macros using other macros (if you want to keep your form of the code):

#define LABEL_A 0
#define LABEL_B 1
#define LABEL_C 2
#define LABEL_D 2

#define VALUE_HASH__0 67490
#define VALUE_HASH__2 67491
#define VALUE_HASH__3 67491
#define VALUE_HASH__4 64759

//append what Index expands to to VALUE_HASH__
#define HASH_LOOKUP(Index) MC_cat(VALUE_HASH__,Index) 
#define MC_cat_(X,Y) X##Y
#define MC_cat(X,Y) MC_cat_(X,Y)

function(const char* LabelAtRuntime){
  int i = getHashCode(LabelAtRuntime);
  switch(i){
    case HASH_LOOKUP(LABEL_A)
      break;
    default:
      break;
}
Petr Skocik
  • 58,047
  • 6
  • 95
  • 142
6

The reason for the error is simply that C does not consider const int LABEL_A=0; to be a compile-time constant. That's unfortunately just how the language is defined. It can be solved by using #define LABEL_A 0 instead.

A third alternative is to use enums, which might be better, as they could be used to tie all your data together and provide a bit more data integrity during maintenance:

typedef enum
{
  LABEL_A,
  LABEL_B,
  LABEL_C,
  LABEL_D,
  LABELS_N
} label_index_t;

typedef void func_t (void);

typedef struct
{
  const char* str;
  int         hash;
  func_t*     func;
} value_t;

...

const value_t VALUE [] = 
{
  [LABEL_A] = { .str = "LABEL_A", .hash = 67490, .func = a_func },
  [LABEL_B] = { .str = "LABEL_B", .hash = 67491, .func = b_func },
  [LABEL_C] = { .str = "LABEL_C", .hash = 67493, .func = c_func },
  [LABEL_D] = { .str = "LABEL_D", .hash = 67459, .func = d_func },
};

_Static_assert(sizeof VALUE / sizeof *VALUE == LABELS_N,
               "Size of VALUE does not match label_t.");

...

// instead of switch(n):
VALUE[n].func();
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • I believe I can try this one...but is it c compatible or c++ compatible? – Amber Beriwal Mar 06 '17 at 14:45
  • @AmberBeriwal C++ might struggle with things like designated initializers. For maximum compatibility with ancient C or with C++, you could write each row as `{"LABEL_A", 67490, a_func},`. Change `_Static_assert` to `static_assert` and include assert.h. – Lundin Mar 06 '17 at 15:01
  • This one is a very good solution, but there is no option to accept multiple answers :D – Amber Beriwal Mar 07 '17 at 04:51
4

Switch case requires the values to be known at compilation time. In your case of array values, the values are not known until runtime.

If you are using c++11, you could use constexpr which forces the compiler to evaluate the array values at compile time. The code below works fine.

constexpr int VALUE_HASH[] = {67490, 67491, 67493, 67459};

int i = getHashCode(LabelAtRuntime);
switch(i) {
  case VALUE_HASH[LABEL_A]:
    break;
  default:
    break;
}
Hesham Attia
  • 979
  • 8
  • 13
2

I don't know what you're really up to. But when I had to implement a menu UI in C, I did it like this:

// Typedef for a menu item's logic function (callback):
typedef void (*menu_item_cb_t)(void*)

// Struct defining a menu item:
typedef struct menu_item {
  const char* label;
  const int hashValue;
  const menu_item_cb_t callback;
  const void* callback_arg;
} menu_item_t;


// Callback for menu item "Do X":
void menu_do_x( void* arg ) {
 // ...
}

// Definition of menu item "Do X":
const menu_item_t menu_item_x = {
  "Do X",
  12345,
  &menu_do_x,
  NULL // Don't need it to do x
}

// All menu items go into one array:
const menu_item_t* MENU[] = { &menu_item_x, ...};
#define MENU_ITEM_CNT xxx

Then you can act on the selected item like:

void menuItemSelected( const char* label ) {
  const int hash = getHashCode(label);
  for ( int i = 0; i < MENU_ITEM_CNT; i++ ) {
    const menu_item_t* const mi = MENU[i];
    if ( hash == mi->hash ) {
      mi->callback( mi->callback_arg );
      break;
    }
  }
}

This approach can of course be varied, but I hope you get the idea. It's basically only to define items with certain properties ("label", "hash", and whatever else) and directly associate them with the function that implements the corresponding action for this item.

JimmyB
  • 12,101
  • 2
  • 28
  • 44
1

If VALUE_HASH is only used to get the switch constants, why not replace it with a jump table?

Note, none of the below is tested or even compiled. There may be syntax errors.

First define a type for the fucntions in the table:

typedef void (*Callback)(/* parameters you need */);

Then you need your actual functions

void labelAProcessing(/* the parameters as per the typedef */)
{
    /// processing for label A
}
// etc

Then your table

Callback valueCallbacks[] = { labelAProcessing, labelBProcessing, ... };

And your code becomes

int i = getHashCode(LabelAtRuntime);
valueCallbacks[i](/* arguments */);

I cannot stress too much that i needs to be validated to ensure that it is a valid index for the valueCallbacks array since otherwise, you might call a random number as if it were a function.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • wouldn't jump table require size equivalent to maximum hash value? – Amber Beriwal Mar 06 '17 at 14:44
  • Hashcode would be a misleading name. You just want a table indexed by label number. i.e. the same as if the hash codes start at 0 and incrementin sequence. – JeremyP Mar 06 '17 at 15:31
1

It is not enough for the operands to be constants. It is not enough for them to be known at compile time (whatever tbat means, the C standard doesn't talk in these terms). The case label must be an integer constant expression.

Integer constant expressions are rigorously defined by the C standard. Roughly, an integer constant expression must be built out of integer constants (also enumerators, character constants, and the like) and cannot contain arrays or pointers, even if they are constant. For an accessible but thirough explanation see e.g. this.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243