switch()

Started by
36 comments, last by mikeman 17 years, 10 months ago
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 intIF do_the_loop == 1 THENBEGIN  INTEGER i;  FOR i=0 TO i<10 DO  BEGIN    array = i;    i++;  END;END;

[grin]
Advertisement
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.
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
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.
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...
Quote:
#define allows you to change the syntax of the language, as you've done with your horrible switch mutilation.


The syntax of begin_switch() is as close to the syntax of real switches as it can be. If I remember correctly, STL has a for_each construct for sequences, no? Or is that syntax "mutilation" ok simply because it pretends to be "valid" by using templates?

Quote:
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.


I'm not talking about the general case. I'm talking about this one. As I said, the syntax of those macros is hardly unfamiliar.

Quote:
Your particular example offers nothing except additional typing...


Additional typing? Exactly as much typing as C/C++ switches require, no more, no less.

Quote:
,minor confusion,


About what?

Quote:
and the possibility that the compiler will not be able to translate the entire switch into a jump table.


Exactly the same possibility if I used a bunch of if{}else if.

Quote:
You also do not maintain the same behavior (fallthroughs are not possible).


Yes they are(notice the switch_hit?).

Quote:
The ability to switch on strings does not constitute a particularly great advantage.


Not just strings. All objects that implement a "==" operator.

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


Really? That's ok then, since my source files already have #includes and templates, that when expanded make them look like a crossover between an Orc and a zombie. Especially those pseudo-variadic template paremeters of Boost with their splendid BOOST_VARIANT_LIMIT_TYPES that are implementation-specific(implementation-specific==when the programmer gets tired of typing T0,T1,T2,T3,..Tn up to infinity) are a true beauty to behold, really, and not at all a hack. So, a tiny bit of additional ugliness wouldn't hurt.

Quote:
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.


Please. Those are not even compareable. That MAPLOOPXY requires that variables of a particular name are declared in order to work, then creates "invisible" temp variables which the programmer is supposed to use, and finally ends with a pair of (apparently) unmatched closing brackets. None of those things begin_switch() has. Whatever temps there are, they're only for internal use and make use of scope, so you can even have nested switches without problems. The programmer doesn't have to work with variables other than the ones that are visible and declared by him, and only in the scope in which he/she declared them, and for the purpose he declared them.

As I said, STL and Boost in many cases horribly abuse the capabilities of templates to introduce functionality that is clearly not a part of C++(lambdas?please), yet noone is complaining because templates are introduced only when C++ came along, so obviously all of their (ab)uses are inherently "correct".

To make things clear: The std::map proposal is certainly better than mine, simply because it's better and more elegant than switch constructs in gereral, "hacked" or not. I simply provided those macros for anyone that, for whatever reason, wanted to use switches for other types except integrals.

[Edited by - mikeman on June 16, 2006 9:54:23 AM]
Quote:Original post by jpetrie
You also do not maintain the same behavior (fallthroughs are not possible).

Actually, it does handle fallthrough. Take a closer look on how switch_hit is used. Was about to say the same when I noticed it...

... and I was 37 seconds late [razz]
Quote:
Additional typing? Exactly as much typing as C/C++ switches require, no more, no less.

You require a couple extra characters of overhead, mostly in the "begin_switch" and "end_switch" parts. case_default is longer than default, as is case_of(foo) compared to case foo:. The typing overhead is relatively minor, however, I should not have listed it first.

Quote:
Yes they are(notice the switch_hit?).

Oops, I did miss that. I stand corrected.

Quote:
The syntax of begin_switch() is as close to the syntax of real switches as it can be. If I remember correctly, STL has a for_each construct for sequences, no? Or is that syntax "mutilation" ok simply because it pretends to be "valid" by using templates?

It's as close as it can be, but its not the same.

for_each() is a different beast; it's a function and its not trying to mimic the syntax of the foreach loops present in other languages. Something more similar to modified switch statements would be:
#define for_each(container_type,container,element)                 for(container_type::iterator element = container.begin();                     element != container.end();                     ++element)


which I'd have about the same adverse reaction to (this is actually far worse than your switch, since its much less generic).

(EDIT: Made macro multi-line so I don't break the thread layout.)
(EDIT: Apparently that didn't work, and the forums are giving me 500's, so just pretend that macro has the appropriate slash characters to make it multi-line.)


The big problem I see with the (switch) construct being confusing is the scope of its use. Do you use it everywhere, or only when you have to? If the former, than the fact that you are potentially losing the jump table optimization is an issue. If the latter, it's not, but now you've introduced multiple ways to do the same thing so you are giving up a little bit of consistancy.

Quote:
Especially those pseudo-variadic template paremeters of Boost with their splendid BOOST_VARIANT_LIMIT_TYPES are a true beauty to behold, really, and not at all a hack. So, a tiny bit of additional ugliness wouldn't hurt.

I disagree; a tiny bit of ugliness does hurt. The more tiny bits of ugliness creep into your code, the uglier and harder to get in to it is overall, and that's a bad thing.

A relative beginner who comes across a syntax-redefining macro like yours will be confused, probably won't understand what's going on, or the subtleties that may be involved in its use. They may attempt to use it, thinking they are following established coding standards (because very few people actually read coding standard documentation, instead they try and pick it up via osmosis by examining existing code), and use it incorrectly or in situations where it shouldn't be (like situations where a regular switch would suffice).

Conversely, an experienced programmer who comes across a syntax-redefining macro will be confused, wonder why on earth it is in place, and attempt to figure out what exactly its doing -- because the assumption will generally be that it does something signifigantly different than the established convention for the language (in this case, an if/else chain). If he discovers it does do something remarkably different, he'll wonder why attempts were made to hide the important differences (being explict is generally better and safer). If he discovers it doesn't do anything remarkably different, he'll wonder why somebody confused the issue with a needless abstraction.

The syntax of the macros is only familiar when you know what the macros do. When you don't, they look familiar (but may not be) and should set off warning flags, because they require either investigation or assumption, both of which can decrease productivity.

Boost is not an issue. While a lot of Boost is wonderful and useful, an equal amount is really damned ugly, and often for very little benefit. They're just as guilty of doing nasty things, and that shouldn't be an excuse. Since nobody has championed their macro and template voodoo as a Good Thing, I don't think bringing it up really strengthens your position at all.
Quote:Original post by mikeman
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.


Actually, when macros hides the natural flow of a program using syntax tricks then I find it less readable than the version without macros. Due to the very nature of macro, I tend to care about what they do because most of the type they are unsafe. In your example, you decided to declare at least two variables in the macros - and the scope of these variables are not controlled very well.

Quote:The syntax of begin_switch() is as close to the syntax of real switches as it can be. If I remember correctly, STL has a for_each construct for sequences, no? Or is that syntax "mutilation" ok simply because it pretends to be "valid" by using templates?


Again, that's very different. for_each() or tranform() or any algorithm in the SCL are algorithms that runs on sequences. They are well defined interfaces and I don't need to know how they are coded - they are part of the standard, thus part of the language. Of course, if one begin to hurt me using "clever syntax tricks" lile
#define loop_on(c) (c).begin(), (c).end()std::for_each(loop_on(my_vector), some_function);

Then I'm going to cry, and i'll probably try to kill him using his own fingers (that would hurt, I believe).

There is also one (albeit minor) thing I don't like about your code: it is not syntax highligted [smile]. yeah, I know, this is a dumb argument

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

Your solution does the exact same thing: it duplicate an existing functionnality and don't really solve any real problem. Worse, you have to choose between an automatic break and no break at all. Writing this would be very difficult:
switch (direction){case "North":case "East":   do_something();  break;case "South":  do_something_else();  break;default:}


There is also another problem I can see:
Quote:Later on, I checked the code again and, having completely forgotten why I put the "do" and "while(false)" there, I removed them.

You found it: we forget things. And the rick to forget something is even greater if your code is not written using easily understandable standards. You might be able to decipher it, but will you be able to fully understand what goes under the hood?

This can be reworded using another example: what if I define my operator+() as a multiplication (meaning that a+b is really a*b)? You'll tell me that I'm subverting the operator+() - I'll tell you that I know I did it, and I'm used to it, so there is no danger. When I read my code, I know that any + is really a *, so I can easily read the code. I don't even understand why my boss fired me.

Of course, since you wrote it and since you're used to it, you'll continue to use it. I am not here to tell what to do what to not do - I'm not good enough to tell anything like this. I juste say that I (as in 'I have my own opinions) find your solution rather dirty. In fact, it doesn't even mean that it is dirty.

Regards,
Quote:Original post by Emmanuel Deloget
Actually, when macros hides the natural flow of a program using syntax tricks then I find it less readable than the version without macros. Due to the very nature of macro, I tend to care about what they do because most of the type they are unsafe. In your example, you decided to declare at least two variables in the macros - and the scope of these variables are not controlled very well.


The variables scope is exactly what it should be. I already said it's possible to have nested begin_switches() with no problem.

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

Your solution does the exact same thing: it duplicate an existing functionnality and don't really solve any real problem. Worse, you have to choose between an automatic break and no break at all. Writing this would be very difficult:
switch (direction){case "North":case "East":   do_something();  break;case "South":  do_something_else();  break;default:}



As hard as this?

#include "stdafx.h"#include <string>#include <iostream>#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);int _tmain(int argc, _TCHAR* argv[]){	std::string direction;	direction="North";	begin_switch(std::string,direction)		case_of("North")		case_of("East")			std::cout<<"Go North or East"<<std::endl;			break;		case_of("South")			std::cout<<"Go South"<<std::endl;			break;		case_default			std::cout<<"Go in other direction"<<std::endl;	end_switch	return 0;}


Really, you guys try do decipher the macro and (incorrectly) assume it doesn't support some behaviours, instead of just taking my word that it does.

Quote:
This can be reworded using another example: what if I define my operator+() as a multiplication (meaning that a+b is really a*b)? You'll tell me that I'm subverting the operator+() - I'll tell you that I know I did it, and I'm used to it, so there is no danger. When I read my code, I know that any + is really a *, so I can easily read the code. I don't even understand why my boss fired me.


Can you point me to the part of the macros that do something fundamentally different than what someone intuitively would assume based on real C/C++ switches? Do they evaluate (a!=b) instead of (a==b)? Do they change the tested variable in any way? Do they change the flow of the program in different ways than those of real C/C++ switches?

I wrote an extensive answer to jpetrie, but the forums ate it. Nevertheless, the points were:

1)Std::for_each: It's not a coincidence they named it that way, is it? A beginner would confuse it with a "for each" loop and try to put a lambda instead of a function. An experienced programmer would wonder why is there a construct named after the universally known for each that does not do the same thing at all, and what it does is hardly exciting.

2)About consistency: I didn't invent switches, you know. C/C++ already has both switches(for integrals) and if-elses that do the same thing for everything,including integrals. Do you use if-else everywhere or just when you can't use switch? If the former, you lose the jump table optimization. If the latter, you lose consistency.[smile]

3)About Boost: Yes, noone championed their macros and templated voodos as a good thing, but everyone is using it.

This topic is closed to new replies.

Advertisement