Sign in to follow this  
darenking

Switch statement in C++ with strings?

Recommended Posts

darenking    122
Howdy! I'm writing a routine that triggers the loading in of sprites (or tile images or whatever) by reading instructions from a text file. The text file contains instructions like this: loadsprites data/001_sprites_paula.bmp loadsprites data/001_sprites_smally.bmp I wanted to do a switch statement like this:
		file.getline(text, 100);

		switch (text)
		{
			case "loadsprites":
				file.getline(text, 100);
				CreateSprites(text);
				break;

			case "loadtiles":
				file.getline(text, 100);
				CreateTiles(text);
				break;

			default:
		}

...so that when it finds the instruction 'loadsprites' it will load in the bitmap named in the next line. But it seems you can't do a switch statement with a string of characters! Any ideas?

Share this post


Link to post
Share on other sites
kusma    170
switch does not work with strings, only integral types. see link for details

try this instead.


if (strcmp(text, "loadsprites") == 0) {
[...]
} else if (strcmp(text, "loadtiles") == 0) {
[...]
} else {
[...]
}

Share this post


Link to post
Share on other sites
Oxyacetylene    426
Depends how extendable you want your system to be. I did something kind of similar in my last project, as I had a quake like console, which could execute commands typed in by the user. The way I implemented it, was to have the console store an array of Command objects. Each command had a method which accepted an array of arguments, and a method that returned a unique integer hash value generated from the commands name.

When the command line is parsed, the console generates a unique hash code from the command name typed in at the command line, and searches its list of commands for a
command that has a matching hash code (and therefore a matching name).

To generate the hash code, I used the CRC32 algorithm. There's an implementation of it in the boost library.

If you don't want to go that far, then something like this would probably suffice.



//Do this once at initialisation time
UInt loadSprites = GenerateHash ( "LoadSprites" );
UInt loadTiles = GenerateHash ( "LoadTiles" );



//At load time
commandName = GetCommandNameFromFile();
UInt commandHash = GenerateHash ( commandName );

switch ( commandHash )
{
case loadSprites:
//Blah

case loadTiles:
//What
}





If you're interested in seeing how I implemented my solution, then the source code to my old project is available here. I think Console.h and Console.cpp are the relevant files. ConsoleCommand.h and ConsoleVariable.h might also be of interest.

Share this post


Link to post
Share on other sites
Quote:
Original post by darenking
Thanks for that reply. That will do the job just fine, but does anyone know of any other solutions?


you could use a std::map< string, enum/int> and simply do a lookup.

Share this post


Link to post
Share on other sites
dimebolt    440
A common way of solving this, is to first convert your string to a numeral like this:

#define INSTRUCTION_TYPE_LOADSPRITES 1
#define INSTRUCTION_TYPE_LOADTILES 2
#define INSTRUCTION_TYPE_UNKNOWN 3

int GetInstructionType(char *text)
{
if (strcmp(text, "loadsprites") == 0) {
return INSTRUCTION_TYPE_LOADSPRITES;
} else if (strcmp(text, "loadtiles") == 0) {
return INSTRUCTION_TYPE_LOADTILES;
} else
return INSTRUCTION_TYPE_UNKNOWN;
}


and then change your switch statement as follows:


file.getline(text, 100);

switch (GetInstructionType(text))
{
case INSTRUCTION_TYPE_LOADSPRITES:
file.getline(text, 100);
CreateSprites(text);
break;

case INSTRUCTION_TYPE_LOADTILES:
file.getline(text, 100);
CreateTiles(text);
break;

default:
break;
}


It could be made even nicer by using an enum, but this should be clear enough.

Note that if you have to compare the same instruction string many times, this conversion to an integer is also faster, because you'll need only one string comparison to get the int.

Tom

Share this post


Link to post
Share on other sites
bytecoder    100
You could try something like this:

typedef void (*CreateFunc)(std::string text);

std::map<std::string, CreateFunc> lookuptable;
lookuptable["loadsprites"] = CreateSprites;
lookuptable["loadtiles"] = CreateTiles;

std::string input = std::getline(file);

if(lookuptable.count(input) <= 0) {
// Error! Invalid command
}

std::string text = std::getline(file);
lookuptable[input](text);

I haven't used C++ in a while, so this might not be completely correct.

EDIT:
Wow, I should really stop taking so much time to post.
Quote:

Depends how extendable you want your system to be. I did something kind of similar in my last project, as I had a quake like console, which could execute commands typed in by the user. The way I implemented it, was to have the console store an array of Command objects. Each command had a method which accepted an array of arguments, and a method that returned a unique integer hash value generated from the commands name.

For the love of god, no! A std::map should do just fine without complicating everything like that. Even if it turns out you need to hash the strings, you should probably use something like std::hash_map.

Share this post


Link to post
Share on other sites
ToohrVyk    1595
EDIT: I am mindnumbingly slow.

Or you could do another way, for instance:


// A function pointer that represent script functions
typedef void (*scriptFunction)(std::istream&);

// A context: it associates a name (the function name or the variable name)
// with an object (a script function)
typedef std::map<std::string,scriptFunction> context;

void CreateSprites(std::istream&);
void CreateTiles(std::istream&);

// Parsing a script document:
void parse( std::istream& in ) {

context c;
c["loadsprites"] = CreateSprites;
c["loadtiles"] = CreateTiles;

string command;

while( !std::getline( in, command ) ) {

c[command](in);
}
}
[/code]

Note: the above is unsafe, you should always check that c[command] is an actual function, or your script can make your program crash.

Additional advantages: if you replace the function pointer by a (non-pointer) object, you can do just about anything.

Share this post


Link to post
Share on other sites
darenking    122
Quote:
Original post by kusma
is there a reason why you need another solution?


Only that it seems less tidy than a switch... so another solution would only be worthwhile (in my case, as I only have half dozen or so instructions) if it's really simple.

Share this post


Link to post
Share on other sites
kusma    170
a switch is usally expanded to a series of compares and/or a jump-table. as you can't just compare the pointers, nor use them for a jump-table, you're stuck with string-compares unless you want to go for something fancy like hashing. anyway, this looks to me like init-code, and if it is... forget optimizing this kind of stuff, it's simply not worth it.

Share this post


Link to post
Share on other sites
Zahlman    1682
The map approach is clean, straightforward, and reasonably optimal (better than the if/else chain of the "most obvious working" code); use it.

If it's *really* speed critical (and I *really highly* doubt it), you might consider a hashed map implementation like stdext::hash_map (I think that's it) rather than std::map (which is a tree map).

Share this post


Link to post
Share on other sites
MaulingMonkey    1728
First, I'd like to point out that there's a much better alternative to:

char text[100];
file.getline( text , 100 );

And that is:

std::string text;
std::getline( file , text );

This will read past 100 characters, expanding text as needed.

I will use this along with std::map to show one of the ways you can achieve the same effect as a switch, if somewhat more split up, which should also handle invalid input files (e.g. with "loadmymomma" instead of a valid command like "loadsprites") without crashing:

void do_loadsprites( std::istream & is ) {
std::string text;
std::getline( is , text );
CreateSprites( text );
}

void do_loadtiles( std::istream & is ) {
std::string text;
std::getline( is , text );
CreateTiles( text );
}

void function_that_had_the_switch_statement( void ) {
std::ifstream file( "MyData.txt" ); //example

std::map< std::string , void (*)( std::istream & ) > do_load_actions;
do_load_actions[ "loadsprites" ] = & do_loadsprites;
do_load_actions[ "loadtiles" ] = & do_loadtiles;

...

//in place of your switch statement:
std::string text;
std::getline( file , text );
if ( do_load_actions.find( text ) == do_load_actions.end() ) {
/* error, text wasn't "loadsprites" or "loadtiles". As such, there's no
* entry in our map for it. You'd place your code for the "default:"
* label here.
*/

} else {
do_load_actions[ text ](); //runs the function pointed at
}
}

Share this post


Link to post
Share on other sites
iMalc    2466
Quote:
Original post by Zahlman
The map approach is clean, straightforward, and reasonably optimal (better than the if/else chain of the "most obvious working" code); use it.

If it's *really* speed critical (and I *really highly* doubt it), you might consider a hashed map implementation like stdext::hash_map (I think that's it) rather than std::map (which is a tree map).
My thoughts exactly.
I would also suggest using a map from a string to an enum, and perhaps do a switch on the enum.
A recent experience has shown that this is a place where hash_map does indeed make an enourmous difference if you have plenty of strings (as we did).

Share this post


Link to post
Share on other sites
kusma    170
Quote:
Original post by Zahlman
The map approach is clean, straightforward, and reasonably optimal (better than the if/else chain of the "most obvious working" code); use it.


if youy have a lot of strings to compare against, yes. however, in this case he had two. now think about it, how much use is there in a tree with two nodes? ;)

[edit:] after reading my reply, i realise i sound like i dislike the map-approach, sorry about that. it IS generaly the superiour way, but i kinda dislike premature optimizations ;)

[Edited by - kusma on July 5, 2005 5:09:01 AM]

Share this post


Link to post
Share on other sites
Zahlman    1682
Yeah, that's why I put "clean" and "straightforward" first and "reasonably optimal" after that (and then only "reasonably", because yes you will just pay overhead for the tree structure with small N). :)

Share this post


Link to post
Share on other sites
Inmate2993    222
Have you considered the Lua scripting language? I know its off topic, but it may be an alternate solution for you, rather than writing this type of parser, you could apply Lua to solve any of your other circumstances that may come up.

Share this post


Link to post
Share on other sites
iMalc    2466
Quote:
Original post by kusma
Quote:
Original post by Zahlman
The map approach is clean, straightforward, and reasonably optimal (better than the if/else chain of the "most obvious working" code); use it.


if youy have a lot of strings to compare against, yes. however, in this case he had two. now think about it, how much use is there in a tree with two nodes? ;)

[edit:] after reading my reply, i realise i sound like i dislike the map-approach, sorry about that. it IS generaly the superiour way, but i kinda dislike premature optimizations ;)
Actually I assumed (and still do) that he simply posted only two cases in his code because it gives you the general idea without posting way more code than necessary.

If in fact he does have only two then just use a couple of it statements and be done with it.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this