31

Possible Duplicate:
C/C++: switch for non-integers

Hi, I need to use a string in switch case. My solution so far was to calculate the hash of the string with my hash function. Problem is I have to manually pre-calculate all my hash values for strings. Is there a better approach?

h=_myhash (mystring);
switch (h)
{
case 66452:
   .......
case 1342537:
   ........
}
Community
  • 1
  • 1
Luke
  • 639
  • 2
  • 6
  • 9
  • 2
    What you describe above is important to know, though you should have other tools in your toolbox, of course. It's often combined with [perfect hashing](http://en.wikipedia.org/wiki/Perfect_hash_function) and code generation (i.e. automatic maintainence, similar to using a compiler-compiler such as yacc instead of writing the underlying boilerplate directly). – Fred Nurk Dec 18 '10 at 23:54
  • That said, this should rarely be the first approach to take to this kind of problem. – Fred Nurk Dec 18 '10 at 23:56
  • 1
    That's a duplicate of [C/C++: switch for non-integers](http://stackoverflow.com/questions/4165131/c-c-switch-for-non-integers) and many similar ones. – sbi Dec 19 '10 at 00:53
  • If performance is so important to you that you can't just check each string with if/else if, then why are you using strings to indicate what to do in the first place? – Karl Knechtel Dec 19 '10 at 04:49
  • You might be interested in my switch implementation: http://bloglitb.blogspot.com/2010/11/fun-with-switch-statements.html – Johannes Schaub - litb Dec 20 '10 at 05:57
  • @EdS. Honestly, coming from C# and Java over to C++, I was actually surprised that C++ switch cases can't include strings by default. Seems like a really intuitive aspect. – Fields Aug 27 '15 at 13:59
  • @Fields: C++ just doesn't hide the fact that `switch(int)` and `switch(string)` have very different performance characteristics. Use an if/else if. – Ed S. Aug 28 '15 at 05:39
  • @EdS. Would you still say if else if for larger scaling projects that need to be maintained? Switches can be significantly more easy to understand than the convolution of very large amounts of if-else statements. Ideally I'd say find a different solution altogether, but between the two a switch definitely has more maintainability. – Fields Aug 28 '15 at 18:46
  • @Fields: I would argue that A) switch v if/elseif is actually *not* a maintainability concern at all, and B) if your conditional chains are that long and they need to be maintained *by hand* then you've already written some difficult to maintain code. – Ed S. Aug 30 '15 at 01:26

10 Answers10

39

Just use a if() { } else if () { } chain. Using a hash value is going to be a maintenance nightmare. switch is intended to be a low-level statement which would not be appropriate for string comparisons.

tenfour
  • 36,141
  • 15
  • 83
  • 142
18

You could map the strings to function pointer using a standard collection; executing the function when a match is found.

EDIT: Using the example in the article I gave the link to in my comment, you can declare a function pointer type:

typedef void (*funcPointer)(int);

and create multiple functions to match the signature:

void String1Action(int arg);
void String2Action(int arg);

The map would be std::string to funcPointer:

std::map<std::string, funcPointer> stringFunctionMap;

Then add the strings and function pointers:

stringFunctionMap.add("string1", &String1Action);

I've not tested any of the code I have just posted, it's off the top of my head :)

Tony
  • 9,672
  • 3
  • 47
  • 75
  • 1
    Could you give me an example using function pointers? – Luke Dec 18 '10 at 23:43
  • 1
    Here's the top Google result: http://www.cprogramming.com/tutorial/function-pointers.html Also have a look at boost libraries for boost::bind (a bit more complex to understand but very flexible) – Tony Dec 18 '10 at 23:45
7

Typically, you would use a hash table and function object, both available in Boost, TR1 and C++0x.

void func1() {
}
std::unordered_map<std::string, std::function<void()>> hash_map;
hash_map["Value1"] = &func1;
// .... etc
hash_map[mystring]();

This is a little more overhead at runtime but a bajillion times more maintainable. Hash tables offer O(1) insertion, lookup, and etc, which makes them the same complexity as the assembly-style jump-table.

Puppy
  • 144,682
  • 38
  • 256
  • 465
3

The best way is to use source generation, so that you could use

if (hash(str) == HASH("some string") ..

in your main source, and an pre-build step would convert the HASH(const char*) expression to an integer value.

ruslik
  • 14,714
  • 1
  • 39
  • 40
2

You could create a hashtable. The keys can be the string and the value can be and integer. Setup your integers for the values as constants and then you can check for them with the switch.

zsalzbank
  • 9,685
  • 1
  • 26
  • 39
2

Ruslik's suggestion to use source generation seems like a good thing to me. However, I wouldn't go with the concept of "main" and "generated" source files. I'd rather have one file with code almost identical to yours:

h=_myhash (mystring);
switch (h)
{
case 66452: // = hash("Vasia")
   .......
case 1342537: // = hash("Petya")
   ........
}

The next thing I'd do, I'd write a simple script. Perl is good for such kind of things, but nothing stops you even from writing a simple program in C/C++ if you don't want to use any other languages. This script, or program, would take the source file, read it line-by-line, find all those case NUMBERS: // = hash("SOMESTRING") lines (use regular expressions here), replace NUMBERS with the actual hash value and write the modified source into a temporary file. Finally, it would back up the source file and replace it with the temporary file. If you don't want your source file to have a new time stamp each time, the program could check if something was actually changed and if not, skip the file replacement.

The last thing to do is to integrate this script into the build system used, so you won't accidentally forget to launch it before building the project.

Sergei Tachenov
  • 24,345
  • 8
  • 57
  • 73
  • I posted a similar solution in one of the duplicates using C++11 and constantexpr to "inline" the hashes of the strings, based on other answers to similar problems (https://stackoverflow.com/questions/4165131/c-c-switch-for-non-integers/45226108#45226108). One of the tricky parts is dealing with a "collision" where the switch key `h` *does not match* a case string but matches the hash. – wawiesel Jul 21 '17 at 06:20
1

You could use the string to index into a hash table of function pointers.

Edit: glib has a hash table implementation that supports strings as keys and arbitrary pointers as values: http://library.gnome.org/devel/glib/stable/glib-Hash-Tables.html

Wang
  • 3,247
  • 1
  • 21
  • 33
1

You can use enumeration and a map, so your string will become the key and enum value is value for that key.

sriks
  • 563
  • 2
  • 6
  • 17
1

If you are after performance and don't want to go through all the if clauses each time if there are many or the need to hash the values, you could send some extra information to the function with the help of enum or just add an enum type to your structure.

Milan
  • 15,389
  • 20
  • 57
  • 65
1

There is no good solution to your problem, so here is an okey solution ;-)

It keeps your efficiency when assertions are disabled and when assertions are enabled it will raise an assertion error when the hash value is wrong.

I suspect that the D programming language could compute the hash value during compile time, thus removing the need to explicitly write down the hash value.

template <std::size_t h>
struct prehash
{
    const your_string_type str;

    static const std::size_t hash_value = h;

    pre_hash(const your_string_type& s) : str(s)
    {
        assert(_myhash(s) == hash_value);
    }
};

/* ... */

std::size_t h = _myhash(mystring);

static prehash<66452> first_label = "label1";

switch (h) {
case first_label.hash_value:
    // ...
    ;
}

By the way, consider removing the initial underscore from the declaration of _ myhash() (sorry but stackoverflow forces me to insert a space between _ and myhash). A C++ implementation is free to implement macros with names starting with underscore and an uppercase letter (Item 36 of "Exceptional C++ Style" by Herb Sutter), so if you get into the habit of giving things names that start underscore, then a beautiful day could come when you give a symbol a name that starts with underscore and an uppercase letter, where the implementation has defined a macro with the same name.

Magnus Andermo
  • 353
  • 4
  • 8