Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is a compile-time checked string-to-int map possible?

I'm probably trying to achieve the impossible, but StackExchange always surprises me, so please have a go at this:

I need to map a name to an integer. The names (about 2k) are unique. There will be no additions nor deletions to that list and the values won't change during runtime.

Implementing them as const int variables gives me compile-time checks for existence and type. Also this is very clear and verbose in code. Errors are easily spotted.

Implementing them as std::map<std::string, int> gives me a lot of flexibility for building the names to look up with string manipulation. I may use this to give strings as parameters to functions which than can query the list for multiple values by appending pre-/suffixes to that string. I can also loop over several values by creating a numeral part of the key name from the loop variable.

Now my question is: is there a method to combine both advantages? The missing compile-time check (especially for key-existence) almost kills the second method for me. (Especially as std::map silently returns 0 if the key doesn't exist which creates hard to find bugs.) But the looping and pre-/suffix adding capabilities are so damn useful.

I would prefer a solution that doesn't use any additional libraries like boost, but please suggest them nevertheless as I might be able to re-implement them anyway.

An example on what I do with the map:

void init(std::map<std::string, int> &labels)
{        
  labels.insert(std::make_pair("Bob1" , 45 ));
  labels.insert(std::make_pair("Bob2" , 8758 ));
  labels.insert(std::make_pair("Bob3" , 436 ));
  labels.insert(std::make_pair("Alice_first" , 9224 ));
  labels.insert(std::make_pair("Alice_last" , 3510 ));
}

int main() 
{      
  std::map<std::string, int> labels;
  init(labels);

  for (int i=1; i<=3; i++)
  {
    std::stringstream key;
    key << "Bob" << i; 
    doSomething(labels[key.str()]);
  }

  checkName("Alice");
}

void checkName(std::string name)
{
  std::stringstream key1,key2;
  key1 << name << "_first";
  key2 << name << "_last";
  doFirstToLast(labels[key1.str()], labels[key2.str()]);
}

Another goal is that the code shown in the main() routine stays as easy and verbose as possible. (Needs to be understood by non-programmers.) The init() function will be code-generated by some tools. The doSomething(int) functions are fixed, but I can write wrapper functions around them. Helpers like checkName() can be more complicated, but need to be easily debuggable.

like image 980
Chaos_99 Avatar asked Sep 01 '13 14:09

Chaos_99


2 Answers

One way to implement your example is using an enum and token pasting, like this

enum {
  Bob1 = 45,
  Bob2 = 8758,
  Bob3 = 436,
  Alice_first = 9224,
  Alice_last = 3510
};

#define LABEL( a, b ) ( a ## b )

int main() 
{      

  doSomething( LABEL(Bob,1) );
  doSomething( LABEL(Bob,2) );
  doSomething( LABEL(Bob,3) );
}


void checkName()
{
  doFirstToLast( LABEL(Alice,_first), LABEL(Alice,_last) );
}

Whether or not this is best depends on where the names come from.

If you need to support the for loop use-case, then consider

int bob[] = { 0, Bob1, Bob2, Bob3 }; // Values from the enum

int main() 
{      
  for( int i = 1; i <= 3; i++ ) {
    doSomething( bob[i] );
  }
}
like image 93
brian beuning Avatar answered Nov 15 '22 09:11

brian beuning


I'm not sure I understand all your requirements, but how about something like this, without using std::map. I am assuming that you have three strings, "FIRST", "SECOND" and "THIRD" that you want to map to 42, 17 and 37, respectively.

#include <stdio.h>

const int m_FIRST = 0;
const int m_SECOND = 1;
const int m_THIRD = 2;

const int map[] = {42, 17, 37};

#define LOOKUP(s) (map[m_ ## s])

int main ()
{
  printf("%d\n", LOOKUP(FIRST));
  printf("%d\n", LOOKUP(SECOND));
  return 0;
}

The disadvantage is that you cannot use variable strings with LOOKUP. But now you can iterate over the values.

like image 37
nickie Avatar answered Nov 15 '22 07:11

nickie