Switch on Strings in C++

Environment: This is standard C++. So you do not need any special environment.

Preface

How many times have you written code like this:

if (!strcmp(pszValue, "Value X"))
  DoThis();
else if (!strcmp(pszValue, "Value Y"))
  DoThat();
else if (!strcmp(pszValue, "Value Z"))
  DoSomethingElse();
else
  DontKnowWhatToDo();

Too many! And those of you who had the chance to take a look at C# might think 'Why can't I code something similar in C++ like this:
switch(strValue)
{
  case "Value X":
    DoThis();
    break;
  case "Value Y":
    DoThat();
    break;
  case "Value Z";
    DoSomethingElse();
    break;
  default:
    DontKnowWhatToDo();
    break;
}

(This code is legal C#. You can switch on strings in C#.)

In this article I will show you a way to implement a switch on strings using pure standard C++.

The Bad News

I can't give you a solution to do exactly what you can do in C#.

The Good News

The Standard Template Library (STL), part of the ANSI/ISO C++ Standard, offers everything needed to get really close to the C# sample. The solution is very simple. You need an enumeration and a std::map, and that's it. The enumeration defines the numeric values use in the switch statement. The std::map contains the link between the valid string values you want to compare some runtime data against, and the numeric enum values you can make a switch on. The string is the key of the map, the enumerator the value.

Here We Go

After thinking about the problem a little bit, there is only one place where to put the enum definition and the std::map definition: The .cpp file. Both, the enum and the std::map, should be declared as static to make them visible only to the code of this particular .cpp file, which helps to avoid global namespace pollution (for more on this see John Lakos, Large Scale C++ Software Design, Addison-Wesley). There is no need to declare them as members of any class since normally the connection between a string value and the action to be taken is very specific to the respective code.

And here comes a sample Switch On String implementation I will use for later discussion:

#include <map>
#include <string>
#include <iostream.h>

// Value-Defintions of the different String values
static enum StringValue { evNotDefined, 
                          evStringValue1, 
                          evStringValue2, 
                          evStringValue3, 
                          evEnd };

// Map to associate the strings with the enum values
static std::map<std::string, StringValue> s_mapStringValues;

// User input
static char szInput[_MAX_PATH];

// Intialization
static void Initialize();

int main(int argc, char* argv[])
{
  // Init the string map
  Initialize();

  // Loop until the user stops the program
  while(1)
  {
    // Get the user's input
    cout << "Please enter a string (end to terminate): ";
    cout.flush();
    cin.getline(szInput, _MAX_PATH);
    // Switch on the value
    switch(s_mapStringValues[szInput])
    {
      case evStringValue1:
        cout << "Detected the first valid string." << endl;
        break;
      case evStringValue2:
        cout << "Detected the second valid string." << endl;
        break;
      case evStringValue3:
        cout << "Detected the third valid string." << endl;
        break;
      case evEnd:
        cout << "Detected program end command. "
             << "Programm will be stopped." << endl;
        return(0);
      default:
        cout << "'" << szInput 
  << "' is an invalid string. s_mapStringValues now contains "
             << s_mapStringValues.size() 
             << " entries." << endl;
        break;
    }
  }

  return 0;
}

void Initialize()
{
  s_mapStringValues["First Value"] = evStringValue1;
  s_mapStringValues["Second Value"] = evStringValue2;
  s_mapStringValues["Third Value"] = evStringValue3;
  s_mapStringValues["end"] = evEnd;

  cout << "s_mapStringValues contains " 
       << s_mapStringValues.size() 
       << " entries." << endl;
}

In looking at the enum definition, you see that the first value is evNotDefined (ev means 'enum value'). Why this? std::map::operator[] can be used for two things: To set a value of a key (as you can see in Initialize()) and to retrieve the value associated with it (look at the switch statement in the main() function). The most important sentence of its description is: 'If this element (key the value [author]) does not exist, it is inserted.'. In relation to our sample above this means, any time we go thru the switch and the value of szInput is new to s_mapStringValues, it is inserted (this is why I added the print out of the map's size) and the value of the new element is set to the initial value of an integral type, which is 0 by default. And as long as there is no special value assigned to the first enumerator of an enumeration, its value is zero too (please refer to the C++ Standard for more on this). That means, if you start the enumeration with a valid value (here: evStringValue1), any unknown (and so unexpected) string value would lead to a valid case of the switch, but to an invalid program behaviour. That's why evNotDefined is the first enumerator.
In case you have to use a given enumeration where an enumerator with value zero is defined, you should call std::map::find() before the switch statement to check if the string value is valid.

The rest of the sample is quite simple. The switch is not made on the string itself but on the numeric value associated to it by the std::map. So there's not really magic in there.

When to Initialize

In this sample, the answer is easy: When the program starts. But unfortunately real life applications aren't as simple as all these samples. So when to initialize in real life?

There are two scenarios we have to cover: The switch is part of a non-static member function, and the switch is part of a static method or global function.

If it is part of a non-static member function, the class' constructor should do initialization, as long as you do not have to care about the runtime behaviour of the ctor. (See comment on 'lazy' init below.) To avoid useless multiple initialization, one should check the size of the map before setting the value. If it is zero, no initialization has been made before, otherwise there is no more need for it.

In case of a static method or global function, you would prefer something similar to C#'s static constructor. But there is nothing like this in C++ (as far as I know). So you do have two choices: 'Lazy' or 'As-soon-as-possible' initialization.

  • 'Lazy' initialization means just before the map is used, a check is done to see if it is filled correctly (again, check its size), and, if not, you will do so.
  • 'As-soon-as-possible' init needs an additional Init method the (class) user has to call in order make sure the switch will work correctly.

Both solutions have their pros and cons. The 'lazy' way leads to additional code before every switch that is using the map and so has an impact on the runtime behaviour. (You can't tell exactly how long the call will take since you do not know whether the string map will be initialized or not.) But the user can't forget to initialize the map. The 'As-soon-as-possible' implies the risk the user has forgotten to call the Init method (but this will be noticed very soon as long as you do some testing on you app), but saves some code when using the map and guaranties runtime behaviour. So it's up to you which way to go. I do prefer the latter one.

Case In-Sensitive

As long as you do not have to use special characters like umlaute (d, |, ...) or whatever, you just have to fill the map with upper- or lower-case-only strings and use _strupr or _strlwr in the switch statement.

A solution for special characters might be a good exercise for the ambitioned reader.

Conclusion

I think this solution is a good sample to show the power of the STL. Hopefully it was able to convince you of the simplicity and elegance of switch statements on string values. Use the STL as much as you can.

Remarks

I hope you do believe me when I say that I worked this out without knowing about the discussions on this topic at comp.lang.c++.moderated or any other discussion group. I have to confess that I did not read thru this discussions since I'm really happy with the solution demonstrated, which has proven its usability in real life. But I do not want to give the impression that I was the first and only one who had the idea of using a map to implement switch on strings in C++.

About the Author

Stefan is Principal Developer at NEW LINE Software Development GmbH in Germany, focused on the development of Windows DNA and Microsoft .NET Web applications. He has published several articles about OS/2 programming and the implementation of the Singleton Design Pattern at EDM/2, and has written the doc and ppt of the chapter 'DNA to .NET Design Path' of the Microsoft .NET Readiness Kit.



Comments

  • Modula-style string switch

    Posted by Jasper Neumann on 04/03/2013 01:45am

    I have created a bunch of solutions of a string switch emulation with a unified interface using macros which are as easy to use as the one of Fred (2002-11-27). My fastest ones utilize a hash map. C++ is not necessarily needed. You will find the detailed description and all free source code on http://programming.sirrida.de/programming.html#c_case_of_string

    Reply
  • Modula-style string switch

    Posted by Jasper Neumann on 04/03/2013 01:43am

    I have created a bunch of solutions of a string switch emulation with a unified interface using macros which are as easy to use as the one of Fred (2002-11-27). My fastest ones utilize a hash map. C++ is not necessarily needed. You will find the detailed description and all free source code on http://programming.sirrida.de/programming.html#c_case_of_string

    Reply
  • Software Developer

    Posted by Ognjen on 11/21/2012 07:14pm

    It's too heavy and too complicated. I'd much rather use if-else blocks instead.

    Reply
  • Thanks

    Posted by Christian Herz on 09/17/2012 05:58am

    Great Post. It was very helpful to me... Sincerely Christian

    Reply
  • "Thanks" to all the jerks flaming someone taking the time to share an idea.

    Posted by Legacy on 04/25/2003 12:00am

    Originally posted by: Jens Winslow

    "Thanks" to all the jerks flaming someone taking the time to share an idea.

    If you want to disagree (or even better, offer an improved idea), fine, do so POLITELY and MATURELY! Even better, why not post your own articles so we all can be awed and humbled by your perfect genius.

    How many people will not share their ideas on this forum after reading your treatment of the poster?

    You may or may not be good code writers, but you have lousy personal skills, and would be a detriment to any programming team. Your attitude would reduce productivity beyond any gains your "perfect" code could ever create.

    Just glad I am not working with you on my team!

    Jens Winslow

    Reply
  • good

    Posted by Legacy on 04/02/2003 12:00am

    Originally posted by: hasan

    It will be very useful who interested.

    Reply
  • What about this one

    Posted by Legacy on 11/27/2002 12:00am

    Originally posted by: Fred

    I'm absolutely sorry
    
    It seems that someone has already proposed something similar on codeguru but elsewhere.

    Note to peoples against macro :
    I think that the less lines of code, the better the code.
    Powerful algorithms are better with bunchs of data. Simple code is often more maintainable.
    I'm french please apologize my english.


    // offers readable code
    // yet not efficient, and not a real switch
    // (case A: case B: don't work)
    // but who cares!!
    // Only tested with MSVC
    // you need to switch a type who handle operator ==


    #include <string>
    using namespace std;

    // Macros ARE powerful
    #define Switch(type, x) { t _X_switch_X_ = x;
    #define Case(x) if(x == _X_switch_X_)
    #define EndSwitch }

    int main(int argc, char**argv)
    {

    string lpszString = "iendfsdf";

    Switch(string, lpszString)
    { Case("a")
    {
    }
    Case("b")
    {
    }
    }
    EndSwitch

    Switch(string, lpszString)
    { Case("c")
    { // work for "c" and "d"
    }
    }
    EndSwitch

    return 0;
    }

    Reply
  • Need Help with a program

    Posted by Legacy on 11/21/2002 12:00am

    Originally posted by: Chris

    Im writing a library system which allows the borrowing of books.The system should be able to find details relating to any book given the name,ISBN etc. and should be able to find details relating to any student given their ID number......any slight help would be appreciated, and i would be grateful if anyone would e-mail me with a sollution.

    Reply
  • Slight improvement in maintainability (I hope)

    Posted by Legacy on 11/18/2002 12:00am

    Originally posted by: Will Bain

    Since so many of the complaints were about maintainability, here's a suggested improvement that replaces the Initialize() method, but still enjoys the inherent efficiency of std::map and the switch statement:
    
    

    static enum StringValue
    {
    evNotDefined,
    evStringValue1,
    evStringValue2,
    evStringValue3,
    evEnd,
    evNumVals // list terminator
    };

    typedef std::map<std::string, StringValue> StringMap;
    typedef StringMap::value_type StringMapValue;

    static const StringMapValue stringMapEntries[] =
    {
    StringMapValue( "First Value", evStringValue1 ),
    StringMapValue( "Second Value", evStringValue2 ),
    StringMapValue( "Third Value", evStringValue3 ),
    StringMapValue( "end", evEnd ),
    };

    static const StringMap s_mapStringValues( &stringMapEntries[evStringValue1], &stringMapEntries[evNumVals] );

    ...followed by the main() function without any call to Initialize().

    (please note: I haven't actually debugged the above code)

    Hope this helps!

    -- WB

    Reply
  • Fine in some case, unusefull in others

    Posted by Legacy on 07/29/2002 12:00am

    Originally posted by: jo

    I'm agree with the last comment : it's really more simple to use a string class if the purpose is only a few comparison of strings.
    But it could be interesting to develop a solution with trees for a lot of string comparison.

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: May 6, 2014 @ 1:00 p.m. ET / 10:00 a.m. PT While you likely have very good reasons for remaining on WinXP after end of support -- an estimated 20-30% of worldwide devices still are -- the bottom line is your security risk is now significant. In the absence of security patches, attackers will certainly turn their attention to this new opportunity. Join Lumension Vice President Paul Zimski in this one-hour webcast to discuss risk and, more importantly, 5 pragmatic risk mitigation techniques …

  • With JRebel, developers get to see their code changes immediately, fine-tune their code with incremental changes, debug, explore and deploy their code with ease (both locally and remotely), and ultimately spend more time coding instead of waiting for the dreaded application redeploy to finish. Every time a developer tests a code change it takes minutes to build and deploy the application. JRebel keeps the app server running at all times, so testing is instantaneous and interactive.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds