Sign in to follow this  

switch()

This topic is 4198 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Dang, I just found that I can't use strings in a switch statement. What can I do to work around this? I tried .c_str() but that doesn't work, presumably because that changes a string to a char* and not an int, but I thought I'd try it anyway. What do I do for this? If there isn't a simple solution, I'll just use a bunch of ifs and else ifs, but now I'm curious and wish to know how to get it to work. Thanks! -Servant of the Lord

Share this post


Link to post
Share on other sites
You can create a std::map<std::string, something> to map strings to integers that you can switch on or function objects that you can invoke.

Share this post


Link to post
Share on other sites
Quote:
Original post by SiCrane
You can create a std::map<std::string, something> to map strings to integers that you can switch on or function objects that you can invoke.


How do I do that? My string is called 'Input' so I would go:

std::map<Input, N_Input>

switch(N_Input)
{
case "north" || "n":
...


And do I need to include special headers, or is it in iostream?

Share this post


Link to post
Share on other sites
He's saying have a map that converts "north" to ID_DIRECTION_NORTH or something like that which is a number. Then switch on the number.

If there are only a few possible strings I would just use a bunch of if/else statements. If there are a large number you could do something like map a string to a function pointer but that's more complicated.

Share this post


Link to post
Share on other sites
Quote:
Original post by Anon Mike
He's saying have a map that converts "north" to ID_DIRECTION_NORTH or something like that which is a number. Then switch on the number.

If there are only a few possible strings I would just use a bunch of if/else statements. If there are a large number you could do something like map a string to a function pointer but that's more complicated.

I currently have 37 possible strings, so I'll try and map them. I still don't quite understand how this is done though, so could someone show me how to or point me to a small tutorial?

Share this post


Link to post
Share on other sites
Something like this:

enum Direction
{
North,
South,
West,
East
};

std::map<Direction, std::string> stringMap;

// In init code:
stringMap[North] = "North";
stringMap[South] = "South";
stringMap[West] = "West";
stringMap[East] = "East";

// When you check the values:
std::map<Direction, std::string>::iterator it = stringMap.find(input);
if(it == stringMap.end())
{
// Invalid input, can't find this string in the map (like the "default" case statement)
}
switch(it.first)
{
case North:
break;

case South:
break;

case West:
break;

case East:
break;
}


Or something like that. There may be a better way to do it, I'm not sure. It's been ages since I used std::map...

Share this post


Link to post
Share on other sites
Thanks. That seems rather chunky for what seems like a simple thing. Someone should create a varation of switch which acepts most variables. I'll mess around with what you've given me though, and see if I can understand it and make switching simpler.

Thanks for all your help everyone.

Share this post


Link to post
Share on other sites
I'd go the other way, and map strings to directionids, not the other way around.


enum
{
NORTH,
EAST,
SOUTH,
WEST
};
std::map<std::string,int> the_map;

the_map["NORTH"]=NORTH;
the_map["N"]=NORTH;
the_map["EAST"]=EAST;
the_map["E"]=EAST;
the_map["SOUTH"]=SOUTH;
the_map["S"]=SOUTH;
the_map["WEST"]=WEST;
the_map["W"]=WEST;

std::string command = getCommand();

std::map<std::string,int>::iterator iter = the_map.find(command);

if(iter != the_map.end() ){
switch(iter->second){
case NORTH:
//go north
break;
case EAST:
//go east
break;
case SOUTH:
//go south
break;
case WEST:
//go west
break;
default:
//unknown command
}
}





Note: you'll need to either come up with a case insensitive string compare, or else ensure that your strings are first converted to the proper case.

Share this post


Link to post
Share on other sites
Quote:
Original post by TANSTAAFL
I'd go the other way, and map strings to directionids, not the other way around.

*** Source Snippet Removed ***

Note: you'll need to either come up with a case insensitive string compare, or else ensure that your strings are first converted to the proper case.


Ugh you beat me to it...

Share this post


Link to post
Share on other sites
Quote:
Original post by Servant of the Lord
Thanks. That seems rather chunky for what seems like a simple thing. Someone should create a varation of switch which acepts most variables.


Some languages offer a construct along these lines, but generally also do not provide fall-through of cases (used normally only when you want the same logic for multiple case values). At this point, it basically becomes syntactic sugar for an if/else-if chain.

The mapping illustrated by Evil Steve is backwards: if you have a string, then you want a map from string to Direction: reverse the types in the declaration, and the items in the initialization (thus stringMap["North"] = North;).

The approach with function objects looks like this, and avoids switch statements altogether:


class DirectionAction {
virtual void operator()();
};

class NorthAction : public DirectionAction {
void operator()() {
// insert logic for going north here.
}
};

// etc.

// HOWEVER, do note that because these are *objects*, you can instantiate them
// several times, and also give them member data. If the logic for going in any
// given direction is effectively the same, then you might just use one class,
// with a "direction" data member that is initialized by a constructor and
// consulted by the operator()().

std::map<std::string, DirectionAction*> mapping;
// If you can avoid inheritance altogether by the above technique, then you
// can store plain objects here and avoid the memory management headache.

mapping["North"] = new NorthAction();
// etc.

std::map<Direction, std::string>::iterator it = stringMap.find(input);
if (it != stringMap.end()) {
(*it->second)();
}
// That is, dereference the iterator to get a key/value pair, select the value
// (which is a DirectionObject*), dereference the pointer to get the object,
// and "call the object" (invoke its operator()()). Look ma, no switch.


You should probably read this as well.

Share this post


Link to post
Share on other sites
Quote:
Original post by TANSTAAFL
I'd go the other way, and map strings to directionids, not the other way around.

*** Source Snippet Removed ***

Note: you'll need to either come up with a case insensitive string compare, or else ensure that your strings are first converted to the proper case.


I convert all my strings to lowercase for comparison already, so that's no problem. My program, which was a simple text adventure ment to give me experience in classes, has now given me much experience. I'm going to rewrite the majority of it, I think, and I'll create a function for easier string comparisons. Thanks for the source, I'm trying to take all this in as best as I can.

[edit:] Thanks Zahlman.


I think I get the drift of it now. Many thanks for all the help!

Share this post


Link to post
Share on other sites
#defines are evil, but IMO sometimes it's ok to use them:


#ifndef INC_UTILS_H
#define INC_UTILS_H

//If SWITCH_C_LIKE is defined, SWITCHes work like C/C++ switches.
//Else, they break out as soon as a CASE is evaluated True.

//#define SWITCH_C_LIKE

#ifdef SWITCH_C_LIKE

#define begin_switch(type,var) {bool switch_hit=false;type switch_var;switch_var=var;{
#define case_of(val) }if((switch_var==val)||(switch_hit==true)){switch_hit=true;
#define case_default }{
#define end_switch }}

#else

#define begin_switch(type,var) {type switch_var;switch_var=var;if(false){
#define case_of(val) } else if(switch_var==val) {
#define case_default } else {
#define end_switch }}
#endif

#endif




It has worked fine in all occasions that I have tried it, although I'm sure there are flaws that I haven't spotted.

Share this post


Link to post
Share on other sites
Quote:
Original post by mikeman
#defines are evil, but IMO sometimes it's ok to use them:

*** Source Snippet Removed ***

It has worked fine in all occasions that I have tried it, although I'm sure there are flaws that I haven't spotted.


O_O

...

Do *NOT* do that.

Share this post


Link to post
Share on other sites
Quote:
Original post by TANSTAAFL
Quote:
Original post by mikeman
#defines are evil, but IMO sometimes it's ok to use them:

*** Source Snippet Removed ***

It has worked fine in all occasions that I have tried it, although I'm sure there are flaws that I haven't spotted.


O_O

...

Do *NOT* do that.


You think so? Why?

Share this post


Link to post
Share on other sites
Quote:
Original post by mikeman
Quote:
Original post by TANSTAAFL
Quote:
Original post by mikeman
#defines are evil, but IMO sometimes it's ok to use them:

*** Source Snippet Removed ***

It has worked fine in all occasions that I have tried it, although I'm sure there are flaws that I haven't spotted.


O_O

...

Do *NOT* do that.


You think so? Why?


For the same reason you don't use recursion to calculate factorials.

Just because you *CAN* do something a particular way doesn't mean that you *SHOULD*.

Share this post


Link to post
Share on other sites
Quote:
Original post by MARS_999
You could use atoi() function if you are using C style strings...


Only if the strings contained numbers, and any that were invalid numbers would evaluate to zero.

If you want to avoid the complexity of using a map, you could always


enum dirs { north,south,east,west };

dirs word_id(string s)
{
if(s=="north") return north;
// etc
}

void f(string s)
{
switch(word_id(s))
{
case north: // etc
}
}



although you probably shouldn't since it would be harder to extend and couldn't be changed to support run-time adding of new words etc.

Share this post


Link to post
Share on other sites
Quote:
Original post by TANSTAAFL
Quote:
Original post by mikeman
Quote:
Original post by TANSTAAFL
Quote:
Original post by mikeman
#defines are evil, but IMO sometimes it's ok to use them:

*** Source Snippet Removed ***

It has worked fine in all occasions that I have tried it, although I'm sure there are flaws that I haven't spotted.


O_O

...

Do *NOT* do that.


You think so? Why?


For the same reason you don't use recursion to calculate factorials.

Just because you *CAN* do something a particular way doesn't mean that you *SHOULD*.


That still doesn't explain why I *SHOULDN'T* use these particular macros. Recursion for factorials adds unnessecery complexity, but here, all these macros do is basically produce the same chain of if...else if you would write yourself by hand. Apart from the minor fact that you can't have variables named "switch_var" and "switch_hit", what exactly is wrong with them? Is the interface hard to grasp, unintuitive? Are they overly slow? Do they cause the stack to overflow? Do they break something else while trying to solve the problem?

Share this post


Link to post
Share on other sites
Quote:
Original post by mikeman
That still doesn't explain why I *SHOULDN'T* use these particular macros. Recursion for factorials adds unnessecery complexity, but here, all these macros do is basically produce the same chain of if...else if you would write yourself by hand. What exactly is wrong with them? Is the interface hard to grasp, unintuitive? Are they overly slow? Do they cause the stack to overflow? Do they break something else while trying to solve the problem?


TANSTAAFL is a bit hard. Finding good arguments to prevent you to use this 'thing' is rather difficult. I have one: it is plain ugly and far less readable than the "if () else if () { } else { }" that you try to avoid.

The is also another problem with your code: there should be only one possible behavior - ie the behavior should not depend on a compile time defined variable (SWITCH_C_LIKE). Because of this define, I can't know which kind of "switch" is used by reading the source code (and the two forms are very different).

Anyway, I can't give you better arguments beside 'that's something I encourage you to NOT do'.

Perhaps this?

#define BEGIN {
#define END }
#define IF if(
#define THEN )
#define ELSE else
#define FOR for(
#define TO ;
#define WITH ;
#define DO ;)
#define INTEGER int

IF do_the_loop == 1 THEN
BEGIN
INTEGER i;
FOR i=0 TO i<10 DO
BEGIN
array[i] = i;
i++;
END;
END;


[grin]

Share this post


Link to post
Share on other sites
Quote:
Original post by Emmanuel Deloget
TANSTAAFL is a bit hard. Finding good arguments to prevent you to use this 'thing' is rather difficult. I have one: it is plain ugly and far less readable than the "if () else if () { } else { }" that you try to avoid.


Are you serious? Sure it's ugly. And so are templates. Or do you refuse to use boost::variant because it's implemented like template<T0,T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,T17,T18,T19>(!)?

Noone cares what's inside the macros. That's not what you're supposed to read when you use them. You compare "if(){}else{}" with the implementation of macros, when you should compare "if(){}else{}" with:


begin_switch(string,s)
case_of("North")
GoNorth();
break;
case_of("South")
GoSouth();
break;
end_switch()







Are you saying that this is less readable?

Quote:

The is also another problem with your code: there should be only one possible behavior - ie the behavior should not depend on a compile time defined variable (SWITCH_C_LIKE). Because of this define, I can't know which kind of "switch" is used by reading the source code (and the two forms are very different).


That is correct. I initially wrote it using only one form that is C-like, I just wrote the other one for my personal use because I like that form more. I use it in my own risk.

Quote:

Anyway, I can't give you better arguments beside 'that's something I encourage you to NOT do'.
Perhaps this?
*** Source Snippet Removed ***
[grin]


That doesn't really solve any problem, it just duplicates an already existing functionality for no reason at all.

The only thing that is really "wrong" with the macros is that they do allow "strange" code to work. Of course, if you go out of your way and write "else if(){}" when you should write "case_of", it will work, since the macros use else-ifs themselves. Personally, I've used them lots of times in my new program and the God Of Correct Programming(tm) has yet to strike me down. It's a tiny trick that is really easy to be used correctly, helps me type a little less code and makes my programs a little more readable. I'm not going to reject it because of some imperfections that will never really arise in any real-world occasion. But I guess people just think different.

Share this post


Link to post
Share on other sites
Well, I'd say it's a bad idea for a few reasons. First of all, non-uglified preprocessor symbols are pretty much always a bad idea. Second, your version is less flexible than a real switch - try putting the default case anywhere except at the end for instance. Thirdly, to address a point from your most recent post:
Quote:
Original post by mikeman
*** Source Snippet Removed ***

Are you saying that this is less readable?

Even if Emmanuel Deloget isn't saying that, all of my compilers are. break is only valid within a loop or a switch statement. You can't use it to break out of an if-else chain. Which means that your hacked switch is never going to be as flexible as a real switch and is likely to cause all sorts of confusion as people have to remember that they should probably always break out of a switch, but never break out of a begin_switch.

Σnigma

Share this post


Link to post
Share on other sites
Quote:
Original post by Enigma
Well, I'd say it's a bad idea for a few reasons. First of all, non-uglified preprocessor symbols are pretty much always a bad idea. Second, your version is less flexible than a real switch - try putting the default case anywhere except at the end for instance. Thirdly, to address a point from your most recent post:
Quote:
Original post by mikeman
*** Source Snippet Removed ***

Are you saying that this is less readable?

Even if Emmanuel Deloget isn't saying that, all of my compilers are. break is only valid within a loop or a switch statement. You can't use it to break out of an if-else chain. Which means that your hacked switch is never going to be as flexible as a real switch and is likely to cause all sorts of confusion as people have to remember that they should probably always break out of a switch, but never break out of a begin_switch.

Σnigma


You know what? You're absolutely correct. This is my first version:


#define begin_switch(type,var) do {bool switch_hit=false;type switch_var;switch_var=var;{
#define case_of(val) }if((switch_var==val)||(switch_hit==true)){switch_hit=true;
#define case_default }{
#define end_switch }} while(false);






Later on, I checked the code again and, having completely forgotten why I put the "do" and "while(false)" there, I removed them. Just now I remembered that they were there to allow me to break out of the begin_switch() just like I would do with any real C/C++ switch. Having used the not C-like switch form myself, I hadn't spotted the problem.

As for the fact that it's ugly and a little less flexible that real switch(the observation about the position of "default" is correct), they're valid observations, but:
(a)I don't really care that it's ugly since they're just 4 lines contained in a small Utils.h file and nothing more, and
(b)If there was a real switch for this thing, I would gladly use it. Except there isn't.

In short, I still think the problems are really minor and don't really deserve worrying about so much. It's not like this is a design decision that the whole program will revolve around. I would feel "guilty" if I was using ugly #defines all over the place to construct my SceneGraph, but for this? I really don't see the reason why I should force myself not to use it. Anyway, this thread has derailed a bit, so just let's just leave it at that.

Share this post


Link to post
Share on other sites
I don't think this is neccessarily a derailing discussion, because the issue of not being able to switch on strings in C++ often leads to similar hacked-up switch macros and it is important and educational to clarify negatives and positives of the idea.

#define allows you to change the syntax of the language, as you've done with your horrible switch mutilation. Changing the syntax of the language does not make the code more readable, it makes it less readable (in the general case) because now others are presented with unfamiliar syntax.

Your particular example offers nothing except additional typing, minor confusion, and the possibility that the compiler will not be able to translate the entire switch into a jump table. You also do not maintain the same behavior (fallthroughs are not possible). The ability to switch on strings does not constitute a particularly great advantage.

The "ugliness" inherent in the solution exists whereever the macro set is used, not just where it is defined.

Although changing the syntax of switch() is relatively benign as far as these sort of things go, its not that far removed from Washu's oh-so-favorite MAPLOOPXY macro. The path of using #define to alter the language's syntax is a dark, slippery slope. If you are using C++, use C++. Everybody who has to work with your code will thank you for it.

EDIT: Trying to find a link to the MAPLOOPXY post in Washu's journal...
EDIT: Got it: MAPLOOPXY. I thought there was more discussion about the overall evil involved in redefining syntax, but I seem to be making that part up. Nevertheless...

Share this post


Link to post
Share on other sites

This topic is 4198 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

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