55

Often I need to choose what to do according to the value of a non-POD constant element, something like this:

switch( str ) {
  case "foo": ...
  case "bar": ...
  default:    ...
}

Sadly switch can only be used with integers: error: switch quantity not an integer.

The most trivial way to implement such thing is then to have is a sequence of ifs:

if( str == "foo" )      ...
else if( str == "bar" ) ...
else                    ...

But this solution looks dirty and should cost O(n), where n is the number of cases, while that piece of code could cost O(log n) at the worst case with a binary search.

Using some data structs (like Maps) it could be possible to obtain an integer representing the string ( O(log n) ), and then use an O(1) switch, or one could implement a static binary sort by nesting ifs in the right way, but still these hacks would require a lot of coding, making everything more complex and harder to maintain.

What's the best way to do this? (fast, clean and simple, as the switch statement is)

ban_javascript
  • 339
  • 1
  • 4
  • 16
peoro
  • 25,562
  • 20
  • 98
  • 150
  • obtain an integer.... is not O( log n ) if n represents the number of options. It's rather O( nbcharacters ). – xtofl Nov 12 '10 at 13:35
  • 8
    If there are enough items in your list that O(n) vs. O(lg n) makes a huge difference, that's probably an indication that you should **not** be using a switch in the first place. – Billy ONeal Nov 12 '10 at 13:37
  • 2
    Possible duplicate: http://stackoverflow.com/questions/4014827/best-way-to-switch-on-a-string-in-c – Johan Kotlinski Nov 12 '10 at 13:42
  • 3
    @kotlinski: that "possible duplicate" is tagged C only, whereas this assumes C++ (See the `str=="foo"` condition, which doesn't work in C). – MSalters Nov 12 '10 at 13:49
  • 1
    I removed the C tag as it's clearly not intended by the OP. – rubenvb Dec 19 '10 at 10:15
  • 1
    @peoro: OK, I'm overruled here, but your point 1 is awfully wrong. Why? Two reasons, both equally bad: a) `"foo"` is a `const char*`, not a `char*`, and b) for a `char` pointer, the equality will not do what is intended (in all situations, some compiler optimizations might make it do what you want, but it's still incorrect): you are comparing pointer values, and not the contents of the array. This is an important difference, and that's why @MSalters comment above and @Kos's answer below for what really happens. I agree, it's an awful example ;) – rubenvb Dec 20 '10 at 19:09
  • @rubenvb: `"foo"` is a string literal, but for the purpose of this question it doesn't matter if it's a `const char*` and a `char*`, I mean, the const-ness has no purpose about the question. You're right about point b (just tested with GCC): `comparison with string literal results in unspecified behavior`... However I hope point 2 still holds. – peoro Dec 20 '10 at 19:16
  • @peoro: I just mention it as a nasty, often-made error... Point two is valid, and either points directly to a `std::map` (and similar) or some ugly macro solution. – rubenvb Dec 20 '10 at 19:20
  • @peoro: Not sure what you mean by "However I hope point 2 still holds". It certainly will not unless both `str = "foo";` appears in the program AND the compiler has a "combine identical string literals" option which is turned on -- and even then it might not. This is an extremely fragile and counterintuitive situation to rely upon. For example, if immediately preceding the `switch` statement you have `str = malloc(100); strcpy(str, "foo");` then the pointer equality is guaranteed to fail, even though the strings pointed to are identical. – j_random_hacker Dec 20 '10 at 20:08
  • @rubenvb: Can't resist a little technical nitpick, your point (a) isn't true for C++ which has auto-conversion of `const char *` string literals to `char *` as a special case (presumably due to the mountains of legacy code that take `char *` parameters), and I believe (not 100% sure) that in C the type of a string literal is simply `char *` anyway. – j_random_hacker Dec 20 '10 at 20:11
  • Here is another neat solution (which I didn't see here): http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htm. Basically, you construct a std::map from strings to an enum, which in turn you switch over. – Hjulle Sep 11 '16 at 00:25
  • Possible duplicate of [Why switch statement cannot be applied on strings?](https://stackoverflow.com/questions/650162/why-switch-statement-cannot-be-applied-on-strings) – Ciro Santilli OurBigBook.com Jan 15 '18 at 15:01
  • 1
    Answers are probably not the same for C and C++. Their string hangling may be different. Please chose one. – Jens Gustedt Jan 15 '18 at 22:56

17 Answers17

60

Using some nasty macro and template magic it's possible to get an unrolled binary search at compiletime with pretty syntax -- but the MATCHES ("case") have to be sorted: fastmatch.h

NEWMATCH
MATCH("asd")
  some c++ code
MATCH("bqr")
  ... the buffer for the match is in _buf
MATCH("zzz")
  ...  user.YOURSTUFF 
/*ELSE 
  optional
*/
ENDMATCH(xy_match)

This will generate (roughly) a function bool xy_match(char *&_buf,T &user), so it must be at the outer level. Call it e.g. with:

xy_match("bqr",youruserdata);

And the breaks are implicit, you cannot fall-thru. It's also not heavily documented, sorry. But you'll find, that there are some more usage-possibilities, have a look. NOTE: Only tested with g++.

Update C++11:

Lambdas and initializer list make things much prettier (no macros involved!):

#include <utility>
#include <algorithm>
#include <initializer_list>

template <typename KeyType,typename FunPtrType,typename Comp>
void Switch(const KeyType &value,std::initializer_list<std::pair<const KeyType,FunPtrType>> sws,Comp comp) {
  typedef std::pair<const KeyType &,FunPtrType> KVT;
  auto cmp=[&comp](const KVT &a,const KVT &b){ return comp(a.first,b.first); };
  auto val=KVT(value,FunPtrType());
  auto r=std::lower_bound(sws.begin(),sws.end(),val,cmp);
  if ( (r!=sws.end())&&(!cmp(val,*r)) ) {
    r->second();
  } // else: not found
}

#include <string.h>
#include <stdio.h>
int main()
{
  Switch<const char *,void (*)()>("ger",{ // sorted:                      
    {"asdf",[]{ printf("0\n"); }},
    {"bde",[]{ printf("1\n"); }},
    {"ger",[]{ printf("2\n"); }}
  },[](const char *a,const char *b){ return strcmp(a,b)<0;});           
  return 0;
}

That's the idea. A more complete implementation can be found here: switch.hpp.

Update 2016: Compile time trie

My newest take on this problem uses advanced c++11 metaprogramming to generate a search-trie at compile time. Unlike the previous approaches, this will handle unsorted case-branches/strings just fine; they only have to be string-literals. G++ also allows constexpr for them, but not clang (as of HEAD 3.9.0 / trunk 274233).

In each trie node a switch-statement is utilized to harness the compiler's advanced code generator.

The full implementation is available at github: smilingthax/cttrie.

smilingthax
  • 5,254
  • 1
  • 23
  • 19
  • 1
    +1 for an interesting library solution that closely approximates what the OP wants. – Billy ONeal Nov 12 '10 at 13:56
  • Interestng. I wonder how this can be developed to be a more robust slution not using macros. Good starting point to a new approach. – John Dibling Nov 12 '10 at 14:02
  • @John: I don't think it's possible w/o macros, as I have to use two templates (template-specializations) per MATCH to calculate the binary search at compile time. It was the closest I could come to a 'switch'. Even the unrolled-ness is unavoidable, dictated by the way template-metaprogramming works. I didn't even believe this would be possible in C++ -- until I got it to compile and saw it work. – smilingthax Nov 12 '10 at 14:18
  • +1 This mimics for non-integer values what the compiler does for integer values. The compiler resorts to jump tables or static binary trees to optimize case lookup (and subsequent branch). But all said, if we are going to do things at runtime to enable this abstraction, then a dictionary, would seem like, is the answer. I mean, OP should not get hung up on making the syntax look similar to a switch statement. A dictionary gives him exactly the same thing at runtime what a switch does for integers. – Ziffusion Nov 12 '10 at 16:58
  • 1
    There is a much better way without macros, in fact: generate a perfect hash function using gperf. http://www.gnu.org/software/gperf/ – Wade Jun 19 '12 at 16:29
29

In C++, you can obtain O(lg n) performance by having a std::map<std::string, functionPointerType>. (In C you could implement what was essentially the same but it would be more difficult) Pull out the right function pointer using std::map<k, v>::find, and call that pointer. Of course, that's not going to be nearly as simple as a language supported switch statement. On the other hand, if you have enough items that there's going to be a huge difference between O(n) and O(lg n), that's probably an indication that you should be going for a different design in the first place.

Personally, I've always found the ELSEIF chain more readable anyway.

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • +1 for suggesting std::map, but elseif is IMO bad for long list of items – BЈовић Nov 12 '10 at 13:43
  • 2
    @VJo: If your list of items is long enough you should replace it with some form of polymorphic behavior anyway. – Billy ONeal Nov 12 '10 at 13:44
  • 1
    In C you can use a sorted list and `bsearch`. The original Awk implementation does this for keywords. In C++, an `unordered_map` does it in O(n). – Fred Foo Nov 12 '10 at 13:48
  • @larsmans: That would be a good C solution. Perhaps an answer we can upvote? :) Though : `unordered_map` is not yet standard, which prevents me from using it in a recommendation (If I won't use it, I don't see how I could ask others to use it), and of course using `bsearch` requires that your strings already be sorted. – Billy ONeal Nov 12 '10 at 13:51
  • @Billy Not necessarily. For example, if you want to map strings to some constant integer values. You can initialize the map using boost::assign::map_list_of – BЈовић Nov 12 '10 at 13:52
16

You can achieve it without using any map or unordered_map like below. Compare first character alone to identify which string. If more than one match, then you can fallback to if/else chain within that case statement. Number of comparisons will be greatly reduced if not many strings starting with same letter.

char *str = "foo";
switch(*str)
{
case 'f':
    //do something for foo
    cout<<"Foo";
    break;
case 'b':
    //do something for bar
    break;
case 'c':
    if(strcmp(str, "cat") == 0)
    {
        //do something for cat
    }
    else if(strcmp(str, "camel") == 0)
    {
        //do something for camel
    }
}

This looks to be optimal solution without any cost, even though its not standard.

bjskishore123
  • 6,144
  • 9
  • 44
  • 66
  • 1
    Why do you say it is not standard ? – Alexandre C. Nov 12 '10 at 21:27
  • @Alexandre: I mean, I used strcmp with c-style string. If we use, std::string instead, then it would be optimal c++ soluiton. – bjskishore123 Nov 13 '10 at 04:43
  • This is actually a pretty nice solution... `:)` – rubenvb Dec 19 '10 at 10:13
  • Have you benchmarked this? It looks like this does nothing but double the number of times the first character needs to be fetched and blow your code size up like crazy. – Billy ONeal Dec 19 '14 at 18:21
  • @Billy: first: the 1st character will be in a cacheline anyway, so that fetch comes almost for free. second: how about: case 'c': if (strcmp(str+1, "at"))... if (strcmp(str+1, "amel")" if you really do care for that single fetch? – blabla999 Sep 08 '15 at 19:56
  • 1
    @blabla999: No, I don't care about the single fetch. I care that you've made the code much more complex and much more likely to be buggy for little/no performance gain. – Billy ONeal Sep 08 '15 at 20:56
  • @Billy: for one: it was you who brought up the "have you benchmarked" issue. Second, it may make a difference if called many times, as it avoids the call to strcmp (for example in a heavily called inner loop). 3rd, this could be wrapped into a macro to make the code more maintainable/readable; 4th: this was more of a "sarcastic" answer to your "doubles the number of fetches" argument, than a suggestion for a production program. But fair enough - no flame war here. – blabla999 Oct 29 '15 at 22:18
  • @bla: Yes, I brought up the "have you benchmarked" point because, as I said, this code is now much more difficult to read and much more likely to be buggy, and I question the claimed performance gain. 2. I don't believe you without showing this is actually true. The optimizer knows what strcmp is. 3. I doubt it. 4. I never made a "double the number of fetches" argument. – Billy ONeal Oct 29 '15 at 22:29
  • @Billy: If there are many words that start with same character, readability can be improved by comparing words in a separate function. I didn't benchmark performance aspect. In real world, few utility functions get called several million times. If the data set is huge, if many words start with same character, and if huge number of calls are required, there will be improvement in performance with this approach. However, this kind of code is not required, if performance is not critical. – bjskishore123 Oct 30 '15 at 07:52
10

Use an if...else block. You don't really have a compelling reason not to, aside from it not being pretty to look at, and the if...else block is the mostr straightforward solution.

Everything else requires additional code which as say say increases complexity. And it just moves the ugliness to elsewhere. But at some level, a string compare still has to happen. Now you've just covered it up with more code.

You might gain some performance increases by using a map or a hash map, but youcan also gain similar if not better gains by simply choosing a smart order to evaluate your if...else blocks. And switching to a map for performance reasons is really just premature micro-optimization.

John Dibling
  • 99,718
  • 31
  • 186
  • 324
  • +1 for "switching to a map for performance reasons is really just premature micro-optimization". The main reason I suggest using something like a `std::map` in places like this is that it helps you maintain [OCP](http://en.wikipedia.org/wiki/Open/closed_principle). – Billy ONeal Nov 12 '10 at 13:45
  • "at some level, a string compare still has to happen" - apparently the questioner is only comparing pointers, which is odd. – Steve Jessop Nov 12 '10 at 13:48
  • @Steve: Really? If so, then he should be able to use a switch. – John Dibling Nov 12 '10 at 13:49
  • @John: by casting to `uintptr_t` or some such, you mean? I'm pretty sure it's an error on the part of the questioner, actually, since there's no guarantee the "same" string literal in different places will have the same address. Just thought I'd mention that the "if/else" code in the question doesn't do string comparisons. – Steve Jessop Nov 12 '10 at 13:52
  • besides, with if-else, you can still optimize a bit by starting with the most expected possibility. – stefaanv Nov 12 '10 at 13:54
  • 1
    @John: oh, no, I take it all back. I missed that the question is about "C/C++", but actually not C at all. So `str` is a `std::string`, and you were right. – Steve Jessop Nov 12 '10 at 13:56
  • @stefaanv: Right, that's what I meant by "youcan also gain similar if not better gains by simply choosing a smart order to evaluate your if...else blocks" – John Dibling Nov 12 '10 at 14:00
  • Easily the best answer here. – Walter Jul 27 '15 at 08:44
  • "premature micro-optimization" isn't the whole fact that switch is const-only a premature micro-optimization? – Spongman May 12 '21 at 15:53
  • @Spongman: Not if you consider that one of the primary benefits of using a switch is code readability. – John Dibling Jul 13 '21 at 23:26
6

In C, there are two common solutions. The first is to keep your keywords in a sorted array, say

typedef struct Keyword {
    const char *word;
    int         sub;
    int         type;
} Keyword;

Keyword keywords[] ={   /* keep sorted: binary searched */
    { "BEGIN", XBEGIN, XBEGIN },
    { "END",   XEND,   XEND },
    { "NF",    VARNF,  VARNF },
    { "atan2", FATAN,  BLTIN },
    ...
};

and do a binary search on them. The previous is straight from the source code of awk by C grandmaster Brian W. Kernighan.

The other solution, which is O(min(m, n)) if n is the length of your input string and m the length of the longest keyword, is to use a finite-state solution such as a Lex program.

Fred Foo
  • 355,277
  • 75
  • 744
  • 836
4

This is similar in spirit to the lambda and the unordered_map solutions, but I think this is the best of both worlds, with a very natural and readable syntax:

#include "switch.h"
#include <iostream>
#include <string>

int main(int argc, const char* argv[])
{
    std::string str(argv[1]);
    Switch(str)
        .Case("apple",  []() { std::cout << "apple" << std::endl; })
        .Case("banana", []() { std::cout << "banana" << std::endl; })
        .Default(       []() { std::cout << "unknown" << std::endl; });    
    return 0;
}

switch.h:

#include <unordered_map>
#include <functional>
template<typename Key>
class Switcher {
public:
    typedef std::function<void()> Func;
    Switcher(Key key) : m_impl(), m_default(), m_key(key) {}
    Switcher& Case(Key key, Func func) {
        m_impl.insert(std::make_pair(key, func));
        return *this;
    }
    Switcher& Default(Func func) {
        m_default = func;
        return *this;
    }
    ~Switcher() {
        auto iFunc = m_impl.find(m_key);
        if (iFunc != m_impl.end())
            iFunc->second();
        else
            m_default();
    }
private:
    std::unordered_map<Key, Func> m_impl;
    Func m_default;
    Key m_key;
};
template<typename Key>
Switcher<Key> Switch(Key key)
{
    return Switcher<Key>(key);
}
Aaron Frantisak
  • 543
  • 5
  • 11
4

Something like that would be too much complex?

#include <iostream>
#include <map>

struct object
{
    object(int value): _value(value) {}

    bool operator< (object const& rhs) const
    {
        return _value < rhs._value;
    }

    int _value;
};

typedef void(*Func)();

void f1() {
    std::cout << "f1" << std::endl;
}

void f2() {
    std::cout << "f2" << std::endl;
}

void f3() {
    std::cout << "f3" << std::endl;
}

int main()
{
    object o1(0);
    object o2(1);
    object o3(2);

    std::map<object, Func> funcMap;
    funcMap[o1] = f1;   
    funcMap[o2] = f2;   
    funcMap[o3] = f3;

    funcMap[object(0)](); // prints "f1"
    funcMap[object(1)](); // prints "f2"
    funcMap[object(2)](); // prints "f3"
}
Simone
  • 11,655
  • 1
  • 30
  • 43
  • 1
    +1 for codifying what I said earlier. Note that you're missing the return type on `main` (which is required in C++, or at least will be in C++0x, can't remember exactly atm) – Billy ONeal Nov 12 '10 at 13:48
3

Here is example code that works:

This should work.
(but ONLY on strings that are 4-bytes or less)

This treats the strings as 4-byte integers.

This is considered, ugly, not portable, "hacky", and not at all good style. But it does do what you wanted.

#include "Winsock2.h"
#pragma comment(lib,"ws2_32.lib")

void main()
{
  char day[20];
  printf("Enter the short name of day");

  scanf("%s", day);

  switch(htonl(*((unsigned long*)day)))
  {
    case 'sun\0':
      printf("sunday");
      break;
    case 'mon\0':
      printf("monday");
      break;
    case 'Tue\0':
      printf("Tuesday");
      break;
    case 'wed\0':
      printf("wednesday");
      break;
    case 'Thu\0':
      printf("Thursday");
      break;
    case 'Fri\0':
      printf("friday");
      break;
    case 'sat\0':
      printf("saturday");
      break;
  }
}

tested in MSVC2010

abelenky
  • 63,815
  • 23
  • 109
  • 159
  • 1
    Not portable. The byte order of multi-char constants is undefined and has no link to the endianess of the platform . – Patrick Schlüter Sep 03 '13 at 12:57
  • on x86 on windows on this implementation of the compiler. I had the case that it changed between two version of the same compiler (gcc v3 vs gcc v4 on Solaris-SPARC). – Patrick Schlüter Sep 03 '13 at 17:15
2

You can use my switch macros, which support all kind of value types. For a few cases, using op== a few times in a row is an order of magnitudes faster than creating map each time and looking up in it.

 sswitch(s) {
    scase("foo"): {
      std::cout << "s is foo" << std::endl;
      break; // could fall-through if we wanted
    }

    // supports brace-less style too
    scase("bar"):
      std::cout << "s is bar" << std::endl;
      break;

    // default must be at the end
    sdefault():
      std::cout << "neither of those!" << std::endl;
      break;
 }
Johannes Schaub - litb
  • 496,577
  • 130
  • 894
  • 1,212
  • Well, this will cost O(n). Of course creating a map will cost so much more (especially if you're using it for three cases like in your example), but it will be worth it, if you've got many cases and run the switch tons of times. – peoro Dec 20 '10 at 18:58
1

You can use any type c/c++ switch implementation. Your code will be like this:

std::string name = "Alice";

std::string gender = "boy";
std::string role;

SWITCH(name)
  CASE("Alice")   FALL
  CASE("Carol")   gender = "girl"; FALL
  CASE("Bob")     FALL
  CASE("Dave")    role   = "participant"; BREAK
  CASE("Mallory") FALL
  CASE("Trudy")   role   = "attacker";    BREAK
  CASE("Peggy")   gender = "girl"; FALL
  CASE("Victor")  role   = "verifier";    BREAK
  DEFAULT         role   = "other";
END

// the role will be: "participant"
// the gender will be: "girl"

It is possible to use more complicated types for example std::pairs or any structs or classes that support equality operations (or comarisions for quick mode).

Features

  • any type of data which support comparisions or checking equality
  • possibility to build cascading nested switch statemens.
  • possibility to break or fall through case statements
  • possibility to use non constatnt case expressions
  • possible to enable quick static/dynamic mode with tree searching (for C++11)

Sintax differences with language switch is

  • uppercase keywords
  • need parentheses for CASE statement
  • semicolon ';' at end of statements is not allowed
  • colon ':' at CASE statement is not allowed
  • need one of BREAK or FALL keyword at end of CASE statement

For C++97 language used linear search. For C++11 and more modern possible to use quick mode wuth tree search where return statement in CASE becoming not allowed. The C language implementation exists where char* type and zero-terminated string comparisions is used.

Read more about this switch implementation.

oklas
  • 7,935
  • 2
  • 26
  • 42
1

LLVM has llvm::StringSwitch that you'd use as follows:

Color color = StringSwitch<Color>(argv[i])
   .Case("red", Red)
   .Case("orange", Orange)
   .Case("yellow", Yellow)
   .Case("green", Green)
   .Case("blue", Blue)
   .Case("indigo", Indigo)
   .Cases("violet", "purple", Violet)
   .Default(UnknownColor);

The major win here is that there are no problems due to hash collisions: no matter what, the actual strings are always compared before a case is accepted.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313
1

It comes to my mind a metaprogramming-based hash generator that you can use like in this example. This one is for c++0x, but I'm sure you can reproduce it similarly for standard C++.

Community
  • 1
  • 1
Diego Sevilla
  • 28,636
  • 4
  • 59
  • 87
1

you could still use a switch.. if you know the labels before hand.. (this is quite nasty (i.e. no checks, but that should be trivial to add as long as you have a valid null terminated string!), I should imagine that this performs faster than most options?

//labels: "abc", "foo", "bar", "ant" "do"

switch(lbl[0])
{
  case 'a':
  {
    switch(lbl[1])
    {
      case 'b': // abc
      case 'n': // ant
      default:  // doofus!
    }
  }
  case 'b':
  {
    switch(lbl[1])
    {
      case 'a': //bar
      default:  // doofus
    }
  }
  case 'd':
  {
    switch(lbl[1])
    {
      case 'o': //do
      default:  // doofus
    }
  }
  case 'f':
  {
    switch(lbl[1])
    {
      case 'o': //foo
      default:  // doofus
    }
  }
}

Of course, if you have a very large list of "labels", this will become quite complicated...

Nim
  • 33,299
  • 2
  • 62
  • 101
  • 2
    Ugh. Removing the extraneous `{}` would make this is a bit more readable, but I'd switch to something like Lex for this. – Fred Foo Nov 12 '10 at 14:23
  • @larsmans: Now if only (f)lex would support Unicode, then all would be good ;) – Billy ONeal Nov 12 '10 at 15:32
  • @larsmans, I did say it was nasty! ;) anyways, it is pseudocode, so needs to be done properly... – Nim Nov 13 '10 at 13:20
0

Some time ago, I have written a templated class achieving some sort of switch equivalent, usable on any data type. However, there are some constraints that limit its application fields:

  • the task to achieve on each branch must be a function call.
  • the functions to call have a single argument (or none, or two, you can tweak the template, but it has to be the same for all functions).
  • the argument value passed to the functions will be the same in every case (but it is given at the moment the switch is executed).

As an example, say, you want to switch on a value of type MyType, if it is equal to value1, call function1("abc"), if it is equal to value2, call function2("abc") (and so on). This would end up as:

// set up the object
//               Type  -        function sig       - function arg. type
SWITCH mySwitch< MyType, void(*)(const std::string&), std::string >;
mySwitch.Add( value1, function1 );
mySwitch.Add( value2, function2 );
mySwitch.AddDefault( function_def );

// process the value
MyType a =...// whatever.
mySwitch.Process( a, "abc" );

Basically, it wraps a std::map container, holding the pair value/function. It can also handle the "default", that makes a switch so interesting. It can be easily adapted to other situations. Here is the code:

template < typename KEY, typename FUNC, typename ARG >
class SWITCH
{
    public:
    SWITCH()
    {
      Def = 0; // no default function at startup
    }

    void Process( const KEY& key, ARG arg )
    {
      typename std::map< KEY, FUNC >::const_iterator it = my_map.find( key );
      if( it != my_map.end() )  // If key exists, call
         it->second( arg );    // associated function
      else               // else, call
        if( Def )       // default function, if there is one.
           Def( arg );  // else, do nothing
    }

    void Add( const KEY& key, FUNC my_func )
    {
      typename std::map< KEY, FUNC >::const_iterator it = my_map.find( key );
      if( it != my_map.end() )
      {
        throw "Already defined !\n";
      }
      my_map[ key ] = my_func;
    }

    void AddDefault( FUNC f )
    {
      Def = f;
    }

 private:
   std::map< KEY, FUNC > my_map;
   FUNC Def; // default function
 };

Other details are here.

kebs
  • 6,387
  • 4
  • 41
  • 70
0

Note that switching by const char* wouldn't work as intended anyway, even if it's was allowed.

A C String is actually a pointer to char. A code like you suggested:

// pseudocode (incorrect C!):
switch(str) {
   case "a": ...
   case "b": ...
}

Provided our language is consistent - it would compare the pointer values, and not the actual string content. Comparing strings needs a strcmp(), so even if the compiler had a special case like "if we're switching against a char*, use strcmp() instead of == (which would probably be poor language design anyway), then anyway it would be impossible for the compiler to make this work like the O(1) hack with integers and jumps.

So don't feel bad for C/C++ as it's not supported. :)

I recommend the O(logn) solution with map (string -> funcptr) or (string -> some abstract object) - if you feel you need scalability here. If you don't, there's nothing particularly wrong with the O(n) solution with else if's. It's still clear, maintainable code, so there's nothing to feel bad about.

Kos
  • 70,399
  • 25
  • 169
  • 233
  • I believe the OP understands all of that. The compiler could still make the switch O(lg n) w.r.t. the number of strings. – Billy ONeal Nov 12 '10 at 13:49
  • I initially thought what you thought, but the question specifies that `str` is non-POD. So it's a `std::string`, not a pointer. In this case "C/C++" actually means "C++", not either of the more-common meanings "C or C++", and "C that also compiles as C++". – Steve Jessop Nov 12 '10 at 13:59
0

Hash your way to Victory

You could use a compile-time hash function like in this glorious stack overflow answer. If you create the functions

  • int_crc32_s that returns a hash of a string at run-time and
  • int_crc32 that returns a hash of a string at compile-time

you are set. To handle a false match of the crc of your key string and case string, you need to include an explicit check for a match. This doesn't really affect the performance because it's just a single check, but it makes it much uglier and the macro version look that much nicer.

I found these two string that have the same CRC32.

//two strings that yield same crc32
const char* collision1="DeferredAmbient_6_1_18-1of2_5";
const char* collision2="PostEffect_Lighting_18_6-0of1_8_14_13-1of2_19";

Without Macros

//without macros (you need to check for collisions)
switch( int_crc32_s(str.c_str()) )
{
    case int_crc32("foo"): if( str=="foo"){std::cout << "foo you\n"; break;}
    case int_crc32("bar"): if( str=="bar"){std::cout << "bar you\n"; break;}
    case int_crc32("baz"): if( str=="baz"){std::cout << "baz you\n"; break;}
    case int_crc32("PostEffect_Lighting_18_6-0of1_8_14_13-1of2_19"):
        if( str=="PostEffect_Lighting_18_6-0of1_8_14_13-1of2_19"){
            std::cout << "jackpot!\n"; break;
        }
    default: std::cout << "just you\n";
}

With Macros

//convenient macros
#define S_SWITCH( X ) const char* SWITCH_KEY(X.c_str()); switch( int_crc32_s(X.c_str()) )
#define S_CASE( X ) case int_crc32(X): if( strcmp(SWITCH_KEY,X) ){ goto S_DEFAULT_LABEL;}
#define S_DEFAULT S_DEFAULT_LABEL: default:

//with macros
S_SWITCH( str )
{
    S_CASE("foo"){ std::cout << "foo you\n"; break; }
    S_CASE("bar"){ std::cout << "bar you\n"; break; }
    S_CASE("baz"){ std::cout << "baz you\n"; break; }
    S_CASE("PostEffect_Lighting_18_6-0of1_8_14_13-1of2_19"){ std::cout << "jackpot!\n"; break; }
    S_DEFAULT{ std::cout << "just you\n"; }
}    

Full Implementation [gist]

// This is a demonstration of using a COMPILE-TIME hash to do a
// switch statement with a string to answer this question.
//
// https://stackoverflow.com/questions/4165131/c-c-switch-for-non-integers
//
// It is based on the StackOverflow question:
// https://stackoverflow.com/questions/2111667/compile-time-string-hashing
//
// And the solution
// https://stackoverflow.com/questions/2111667/compile-time-string-hashing/23683218#23683218
//

#include <iostream>
#include <string>
#include <vector>
namespace detail {

// CRC32 Table (zlib polynomial)
static constexpr uint32_t crc_table[256] =
{
    0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
    0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
    0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
    0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
    0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
    0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
    0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
    0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
    0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
    0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
    0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
    0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
    0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
    0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
    0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
    0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
    0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
    0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
    0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
    0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
    0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
    0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
    0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
    0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
    0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
    0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
    0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
    0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
    0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
    0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
    0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
    0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
    0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
    0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
    0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
    0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
    0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
    0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
    0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
    0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
    0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
    0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
    0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};

//constexpr combine
template<size_t idx>
constexpr uint32_t combine_crc32(const char * str, uint32_t part) {
  return (part >> 8) ^ crc_table[(part ^ str[idx]) & 0x000000FF];
}

//constexpr driver
template<size_t idx>
constexpr uint32_t crc32(const char * str) {
  return combine_crc32<idx>(str, crc32<idx - 1>(str));
}

//constexpr recursion stopper
template<>
constexpr uint32_t crc32<size_t(-1)>(const char * str) {
  return 0xFFFFFFFF;
}

//runtime combine
uint32_t combine_crc32_s(size_t idx, const char * str, uint32_t part) {
  return (part >> 8) ^ crc_table[(part ^ str[idx]) & 0x000000FF];
}

//runtime driver
uint32_t crc32_s(size_t idx, const char * str) {
  if( idx==static_cast<size_t>(-1) )return 0xFFFFFFFF;
  return combine_crc32_s(idx, str, crc32_s(idx-1,str));
}

} //namespace detail

//constexpr that returns unsigned int
template <size_t len>
constexpr uint32_t uint_crc32(const char (&str)[len]) {
  return detail::crc32<len - 2>(str) ^ 0xFFFFFFFF;
}

//constexpr that returns signed int
template <size_t len>
constexpr int int_crc32(const char (&str)[len]) {
  return static_cast<int>( uint_crc32(str) );
}

//runtime that returns unsigned int
uint32_t uint_crc32_s( const char* str ) {
  return detail::crc32_s(strlen(str)-1,str) ^ 0xFFFFFFFF;
}

//runtime that returns signed int
int int_crc32_s( const char* str) {
  return static_cast<int>( uint_crc32_s(str) );
}

//convenient macros
#define S_SWITCH( X ) const char* SWITCH_KEY(X.c_str()); switch( int_crc32_s(X.c_str()) )
#define S_CASE( X ) case int_crc32(X): if( strcmp(SWITCH_KEY,X) ){ goto S_DEFAULT_LABEL;}
#define S_DEFAULT S_DEFAULT_LABEL: default:

int main()
{
    std::string str;
    std::cin >> str;

    //two strings that yield same crc32
    const char* collision1="DeferredAmbient_6_1_18-1of2_5";
    const char* collision2="PostEffect_Lighting_18_6-0of1_8_14_13-1of2_19";

    //without macros (you need to check
    switch( int_crc32_s(str.c_str()) )
    {
        case int_crc32("foo"): if( str=="foo"){std::cout << "foo you\n"; break;}
        case int_crc32("bar"): if( str=="bar"){std::cout << "bar you\n"; break;}
        case int_crc32("baz"): if( str=="baz"){std::cout << "baz you\n"; break;}
        case int_crc32("PostEffect_Lighting_18_6-0of1_8_14_13-1of2_19"):
            if( str=="PostEffect_Lighting_18_6-0of1_8_14_13-1of2_19"){
                std::cout << "jackpot!\n"; break;
            }
        default: std::cout << "just you\n";
    }

    //with macros
    S_SWITCH( str )
    {
        S_CASE("foo"){ std::cout << "foo you\n"; break; }
        S_CASE("bar"){ std::cout << "bar you\n"; break; }
        S_CASE("baz"){ std::cout << "baz you\n"; break; }
        S_CASE("PostEffect_Lighting_18_6-0of1_8_14_13-1of2_19"){ std::cout << "jackpot!\n"; break; }
        S_DEFAULT{ std::cout << "just you\n"; }
    }
}
wawiesel
  • 170
  • 9
0

This answer is an additional work on the answer https://stackoverflow.com/a/4165312/10075467 for the same question by smilingthax, with modification of the Switch function to get result without the actual need of sorting (the purpose being achieved with map instead, complexity is still affected).

#include "iostream"
#include "algorithm"
#include "cstring"
#include "map"
#include "typeinfo"

using namespace std;

template <class key_t, class val_fn>
void Switch(const key_t switch_key, auto switch_cases, val_fn default_case = []{printf("not found\n");} )
{
    //using key_value_pair = pair<const key_t, val_fn>;
    for(auto x : switch_cases) { cout<<x.first<<" : "; x.second(); cout<<"\t"; } cout<<endl;
    auto match = switch_cases.find(switch_key);
    if(match == switch_cases.end()) { default_case(); return; }
    match -> second();
    //cout<<typeid(switch_cases).name();
}

int main()
{
    string switch_key = " 6ZG ";
    //getline(cin, switch_key);
    map <string, void (*)()> switch_cases = {
            { "gef", []{ printf("0\n"); } },
            { "hde", []{ printf("1\n"); } },
            { "ger", []{ printf("2\n"); } },
            { "aTe", []{ printf("0\n"); } },
            { "ymX", []{ printf("1\n"); } },
            { "zcx", []{ printf("16\n"); } },
            { "i5B", []{ printf("17\n"); } },
            { "5ui", []{ printf("18\n"); } },
            { "zkB", []{ printf("19\n"); } },
            { " zxw ", []{ printf(" 0 \n"); } },
            { " Aq0 ", []{ printf(" 1 \n"); } },
            { " uSa ", []{ printf(" 2 \n"); } },
            { " 3pY ", []{ printf(" 3 \n"); } },
            { " 6ZG ", []{ printf(" 4 \n"); } },
            { " PHT ", []{ printf(" 5 \n"); } },
            { " Jv9 ", []{ printf(" 6 \n"); } },
            { " 0NQ ", []{ printf(" 7 \n"); } },
            { " 4Ys ", []{ printf(" 8 \n"); } },
            { " GzK ", []{ printf(" 9 \n"); } }
        };
    Switch<string, void (*)()> ( switch_key, switch_cases);//, [](const string a, string b){ return a!=b;} );

    return 0;
}

Let map do the sorting thing for us. If we have an already sorted list of switch_cases, it'd be better again to use the initializer_list.

Edit: Recently used the same for one of me codechef submission to a (simple) problem of laddus. Here I have used it to return integer type instead of void.

Himanshu Tanwar
  • 198
  • 1
  • 11