7

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.

Chaos_99
  • 2,284
  • 2
  • 25
  • 29
  • 1
    It sounds like you want to construct the strings at runtime, but somehow have this checked at compile-time? – Oliver Charlesworth Sep 01 '13 at 14:20
  • There are ways to convert enums to their appropriate strings, so it should be possible.. Though hard to see how it is compile time if you are building the strings in run time – Karthik T Sep 01 '13 at 14:20
  • `std::map` certainly has the capability to tell you whether an element was inserted. One way is `insert`. – chris Sep 01 '13 at 14:20
  • If the exception is a dealbreaker than write a wrapper that throws an exception.. – Karthik T Sep 01 '13 at 14:21
  • You can use [`map::at`](http://www.cplusplus.com/reference/map/map/at/) to get an exception on invalid key – Karthik T Sep 01 '13 at 14:22
  • 4
    You shouldn't use `map[index]` if you want to know if the value is in the map! You could use `map.find(index)` to get an iterator to the object or `map.end()` if it isn't there or `map.at(index)` to get an exception when accessing a non-existing element. – Dietmar Kühl Sep 01 '13 at 14:24
  • Yes, I'm essentially asking for a compile-time check for run-time data. That's why I called it impossible. It's that the data is not really run-time, as all is pre-defined in the code. It's completely deterministic. The names are not generated from user-input. The loops run for fixed limits. I'll add some samples to the question .. – Chaos_99 Sep 01 '13 at 14:26
  • I get the names and their values are defined before the process starts. I get you want to do some name lookups in your code. I do not get where the names are coming from. Are you reading a data stream with the names or are the name references embedded in your code. – brian beuning Sep 01 '13 at 14:35
  • @brian As you can see in the example, everything is hard-coded. (Actually it's the product of some code-generation tools.) – Chaos_99 Sep 01 '13 at 14:50
  • 1
    I think you can do this with a lot of metaprogramming magic. Point is that is hard (but possible) to encode strings inside a type name. Honestly I wouldn't recommend this, since it may take years just to compile (and every compiler has a maximum limit on the number of variadic template arguments, probably < 3000). – sbabbi Sep 01 '13 at 15:02
  • 1
    @Chaos_99 There are lots of ways to do this. We need to know more about your code to pick the best. An enum and token pasting would be the fastest at runtime. – brian beuning Sep 01 '13 at 16:13
  • @brian Using the preprocessor and token pasting is an excellent idea to solve the prefix/suffix problem and even the looping if the index is at the beginning/end of the name. Post this as an answer please. – Chaos_99 Sep 01 '13 at 16:50

4 Answers4

1

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.

nickie
  • 5,608
  • 2
  • 23
  • 37
  • Interesting idea. Limits the looping to consecutive values, but that's actually quite acceptable. Unfortunately, it's also hard to debug (try to find the 1276th value in the array). – Chaos_99 Sep 01 '13 at 15:02
1

Maybe something like this (untested)?

struct Bob {
    static constexpr int values[3] = { 45, 8758, 436 };
};

struct Alice {
    struct first {
        static const int value = 9224;
    };
    struct last {
        static const int value = 3510;
    };
};

template <typename NAME>
void checkName()
{
    doFirstToLast(NAME::first::value, NAME::last::value);
}

...

constexpr int Bob::values[3]; // need a definition in exactly one TU

int main() 
{
    for (int i=1; i<=3; i++)
    {
       doSomething(Bob::values[i]);
    }

    checkName<Alice>();
}
Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • Good idea, but not possible in my case. The complexity of the code-generator filling the list would have to be boosted enormously. I can modify how to output each line, but I can't add complex decision-making. – Chaos_99 Sep 01 '13 at 17:14
1

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] );
  }
}
brian beuning
  • 2,836
  • 18
  • 22
  • Albeit using the preprocessor (which comes with its own problems during debugging), this solves the problem rather nicely and produces quite verbose and easy to understand code. I'll need to check within the real code if it really ticks all boxes, but I already like it. – Chaos_99 Sep 01 '13 at 19:58
  • Can't do for-loops though, as this would still be only available at runtime. Or is there a non-evil trick to do for-loops with the preprocessor? – Chaos_99 Sep 01 '13 at 20:07
0

Using enum you have both compile-time check and you can loop over it:

How can I iterate over an enum?

Community
  • 1
  • 1
cpp
  • 3,743
  • 3
  • 24
  • 38
  • The link says I can't loop over it if I use arbitrary numbers for the ENUM, which would be the case if I use the enum to store a name-to-int map. – Chaos_99 Sep 01 '13 at 15:00