views:

914

answers:

21

I just saw this block of code on the Wikipedia article on conditional operators:

Vehicle new_vehicle = arg == 'B' ? bus      :
                      arg == 'A' ? airplane :
                      arg == 'T' ? train    :
                      arg == 'C' ? car      :
                      arg == 'H' ? horse    :
                      feet;

I've changed the code a little, but the idea is the same. Would you find this use of the conditional operator acceptable? It's much more concise than the if-else construct, and using a switch would definitely open up a whole new set of opportunities for bugs (fall-throughs anyone?). Also, if-elses and switch can't be used as R-values, so you'd have to create the variable first, initialize it and then assign as necessary.

I for one really like this, but I'm wondering what others think.

But the formatting is essential.

EDIT: I still like this. But I understand those who say "the switch statement was made for this". OK, maybe so. But what if the conditions are function calls that return bool? Or a million other things you can't switch on.

Are you switch lovers really trying to convince me that a huge if-else chain is better? Yes, programmers who don't know how to use the conditional operator will not understand this. They should learn how to use it. It's not arcane.

+30  A: 

I would use a switch, because this is the kind of thing it was designed for. Yes, there is a risk of fall-through bugs, but this nested-conditional block has a much higher risk of being misunderstood by other programmers.

Josh Yeager
I'd append " ... who don't understand or regularly use the ternery operator"
Charles Bretana
I agree, don't be scared of accidental fall-throughs, That's like being scared of while loops because you may accidentally 'break' out of it.
Trent
I had to dummy edit your answer because SO was claiming my non-existing vote was too old to be changed... sorry.
Martinho Fernandes
@Charles, i would not append that. Having multiple nested conditional operators is not a common thing to do, and the above code is readable only because of the special indentation and in-line spacing. Would it be written another way, or displayed another way, i believe it would look much more complicated.
Johannes Schaub - litb
@Johannes: I fail to see the relevance. The code is marvelously easy to read as is. It could certainly be made hard to read with different formatting, but I could also make a mess of a `switch` statement. @Josh: This is not what the `switch` statement was designed for. If it was, it would be some sort of operator or function-like thing, and would return a value.
David Thornley
@Johannes, *all code* is readable only because of the special indentation and in-line spacing. Since chained ternary operators are an uncommon sight, it means that the formats often differ wildly and a semi-"standard" way of doing so hasn't really reached the masses.
Justin Johnson
@Josh Regardless of whether or not a `switch` is well suited for this type of logic, it doesn't preclude other constructs and language features from being equally or better suited for the same task. IMHO, a `switch` would be far more cumbersome in this situation.
Justin Johnson
@Johannes, while admnitting my comment was a bit tongue in cheek, I'd say (albiet redundantly, obviously and a bit axiomatic) that for those who use this construction frequently, and are familiar and comnfortable with it, it is indeed quite common. As to your comment about the readability and formatting, well, for what technique would your comment not be true about ?
Charles Bretana
There are cases where switch can't be used. For example, if new_vehicle is a reference, instead of a stack-allocated instance of the Vehicle class.
Franci Penov
@Charles: *most* code is readable even if you change the indentation a bit. For most code, I can tell my editor (whether it's Vim, Emacs or Visual Studio) to auto-indent my code, and trust that the result is readable. Can I do that with this sample? I think that's a real problem
jalf
I like switch too, I would love to be able to use other things in my switch conditions that just built-in types though... The go programming language had a rather interesting idea there, as for me, in C++ and at the moment, switch is broken.
Matthieu M.
@David, code should be understandable no matter the indentation: You should be able to fix indentation when needed, also when accidentally some tab/spaces got out of sync. However for the above code the readability and whether one "understands" (actually, I would guess) it depends on indentation. For instance, is it parsed like `(arg == 'B' ? bus : arg == 'A') ? ...` or parsed like `arg == 'B' ? bus : (arg == 'A' ? ...)` ? Most (including me) wouldn't know off head. And that is evil: I don't want to consult the Standard for understanding basic C++ code.
Johannes Schaub - litb
+30  A: 

I have used this type of construction many times. As long as it's formatted nicely (i.e. not all on one line, making it unreadable), I don't see a problem with it.

Graeme Perrow
+10  A: 

I like it. It is similar to an if-else-if ladder, only more concise.

Avi
+13  A: 

This is a great example of conditional operator use. I use it in this manner all the time in C++, Java, and Perl.

NB
+5  A: 

A switch is both clearer and possibly much more efficient. If I saw code like this at a code review, I'd be worried. Also, this is "the conditional operator" - it is an instance (albeit currently the only one in C and C++) of a ternary operator.

anon
It could be more efficient if you are switching on the value of a variable, but what if it's for instance a succession of hash.contains(arg) conditions?
Lucas
Then I would use an if-ladder, or possibly a hash map of values to functions.
anon
Actually in this case a compiler would probably generate similar code. It may be, in the compiler's opinion, too sparse to use a jump table.
Richard Pennington
+12  A: 

Not only is there nothing wrong with it, it communicates the intent of the operation in the most concise and clear way possible.

Replacing with if else, or switch construction requires that the snippet

"new_vehicle =  "

be repeated in every instance, which requires that the reader read every repeating instance of it to ensure that it is in fact the same in every instance..

Charles Bretana
You put the switch in a function. If you want to do this operation in one place, there is a good chance you will want to do it in another.
anon
What !?? Then, in the function,m inside the switch, you will have to repeat the "new_vehcle =" once for each case inside the switch.. using a function does not avoid this, it just moves it to the function.
Charles Bretana
for an example of what it looks like in a function, see my answer. the "new vehicle =" part is only needed once. and yes, in the function, i do have to type "return" several times. But the compiler will catch me if i make a typo and type retrn.
Peter Recore
@Charles - No, in the function, you wouldn't be assigning to `new_vehicle` you'd be returning the value. Then in your code you'd say `new_vehicle = function(arg);` But it's all semantics. You could also make `function()` a macro that expands inline to the above ternary conditional. Would it be somehow less readable or worse (assuming a safely written macro that properly handles all macro expansion) than a function or a switch statement?
Chris Lutz
@Peter, @Chris, ahhh, yes, i understand what u mean now. Your're saying that the switch cases in the function would each contain only a return statement. For me at least, that construction would be equally clear and readable to the ternery in the question ...
Charles Bretana
@peter, @chris, biut even in a function I would prefer the ternery construction rather than a switch case, as, using the ternery, each successive condition can be any expression that evaluates to a boolean, whereas using a switch case, the case statements must all be either integer or string values...
Charles Bretana
+5  A: 

Purely a stylistic choice. For small data sets like you present here, then as long as your programming team isn't thrown by such a thing, then its fine in my book.

Mordachai
If your team _is_ thrown by such a thing, they need a quick refresher on the C programming language, and you should consider whether or not this is the team you want to work on.
Chris Lutz
lol - perhaps. :)
Mordachai
+2  A: 

I've never seen anything written like this before. While it is clever and well-formatted, this seems like the perfect opportunity to use a dictionary/hashtable (assuming Vehicle is an enumeration, which is unclear).

Jon Seigel
No such thing in C, unless you write it yourself or find somebody else's code.
David Thornley
The question is also tagged with C++.
Jon Seigel
@David This question is also tagged as `C++` which, as you likely know, has the stl `std::map` class that can be used as @Jon suggests
Justin Johnson
Yup, I'm aware of `std::map`, but it doesn't exist in C, and the question is tagged C as well as C++.
David Thornley
Regardless of whether or not there exists a dictionary/hashtable as native in the language, I stand by my original answer.
Jon Seigel
@Jon: I'd say it would depend on how often it was used, I'd say. For just a few values like this, it may not be worth creating a map in C++, and I'd be even more reluctant to bring something like that in in C. If this was used more than once in the code, I wouldn't want to duplicate even this if I could avoid it, and a function of some sort (which may be a map) would be much better.
David Thornley
@David: Agree completely. I think the original question lacks context, which is important here.
Jon Seigel
A: 

Many of us are used to the Iif-functions in various reporting tools or the If-function in Excel where we basically need to use the less clear Iif(arg="B";"Bus";Iif(arg="A";Airplane;"Feet")). I love your sample compared to that :) I personally would've used if-elses, but I would not have a problem with your sample.

Oliver John
+2  A: 

I would lean toward a switch statement because the compiler will catch duplicate cases. Arguably, this is not an issue in this example but if the list gets really long and is worked on by several different people it is easy to add a duplicate and not realize it.

semaj
In other words, if this were different, it would be best formatted differently.
David Thornley
@David Thornley - Actually no. It would be a better practice to use a switch statement as it stands. Even though the list may be small enough to see duplicates now, when it gets added onto, it is more likely that any duplicates will not be caught.
semaj
+8  A: 

The conditional operator version is clean, simple, and it's immediately obvious to anybody who knows C or C++ what is happening. Its other virtue is that it returns a value immediately, which means it can be put in the initialization (like this example is).

A switch statement would be more clumsy. It would require that the variable be declared and then initialized, usually a bad idea if it can be avoided. It would take more typing, and would have more places for bugs to creep in. It wouldn't be as clear, since it would be necessary to look at each case to see that it said something like new_vehicle = foo; break;.

If you're going to do this look up here only, then having the conditional version right there is good, as it immediately shows what's happening. If it's going to occur more than once, consider putting it in a function, so that there's only one place to update if anything changes (such as, say, 'R' for carriage or 'L' for helicopter).

David Thornley
+1 for "it's immediately obvious to anybody who knows C or C++ what is happening." Anyone who doesn't know how to parse a (well-formatted and relatively benign) ternary conditional statement (or two) _doesn't know C_ and is probably missing a large part of C++. You wouldn't avoid using `malloc()` in your code simply because the next dope who comes along might not understand pointers and memory management, would you?
Chris Lutz
+2  A: 

Read the C++ section of the Wikipedia article a bit more carefully. It explicitly lists some situations where using the ?: operator is the only option, and can't be replaced by if/else or switch.

On the other hand, I wouldn't use it only because it looks prettier.

Franci Penov
Why not use it only because it looks prettier? Easy-to-read code is good, in general.
David Thornley
"Pretty" is not the right reason to write code. :-)
Franci Penov
On the other hand, given two ways to write something, the prettier is often the easier to read, and therefore the better. Writing code just to be pretty is of course best reserved for things like the International Obfuscated C Code Contest.
David Thornley
Programming is a craft, in my book. And as such, beauty is most certainly a part of what I create. Certainly, my best work has that quality, and I often choose how to format sections of code to be both easily intelligible by others, and to have a certain pleasing aesthetic.
Mordachai
It lists situations where you cannot replace ?: with an if/else -- unless you replace the conditional operator with a function call and put the if/else in the function call. So ?: isn't truly the ONLY option, just the cleanest one.
Eddie
?: operator results in an expression, rather than a statement, which is where you should use it.
kyoryu
+5  A: 
Vehicle new_vehicle = getVehicleByType(arg);

Vehicle getVehicleByType(char arg){
  if (arg == 'B') return bus;
  if (arg == 'A') return airplane;
  if (arg == 'C') return car;
  if (arg == 'T') return train;
  if (arg == 'H') return horse;
  return feet;
}

I like this better. The nested conditional is clever, but I think this is almost as concise and less likely to confuse a future reader. Sorry if the syntax is off, I'm not doing much C nowadays.

EDIT: Fixed return type omission noted in comments. thx!

EDIT: I'm not horrified at your version by the way. I did not exclaim WTF or OMG when i saw it. I just prefer mine a little more :)

Peter Recore
You missed the function return type. And I'd still use a switch.
anon
I'd rather use a switch statement here than a series of `if`s, personally. But we're bound to disagree, as I think the given code is fine as it is.
Chris Lutz
This technique moves the code that does the mapping away from the code that does the assignment. That might be a good or a bad thing, depending on your viewpoint. Sometimes it's nice not to have to scroll back and forth in the code to see what's going on.
Mark Ransom
What's so confusing about a ternary operator?
Justin Johnson
I'm not saying it would confuse me. I do think that given a random low experience programmer stuck with maintaining a program they didn't write, they would be more likely to understand a bunch of if's than a clever use of nested ?:'s Ultimately, this really is probably more a style preference question than one that has a "right" answer.
Peter Recore
@Mark - good point. Especially if this mapping code is ever only going to be used once. As soon as you use it a second time though, it belongs in a function. Although, i would put this function right above/below the function that uses it. (Assuming i wasn't using a language that let me define functions local to functions, which i prefer anyway :)
Peter Recore
+2  A: 

A few people have already mentioned the possibility of using an std::map or other associative array type to do the job. As long as you're only doing this is one place (or a few places), you might consider just using a normal array or vector instead:

Vehicle vehicles[CHAR_MAX];

// Initialization    
std::fill_n(vehicles, CHAR_MAX, feet);
vehicles['A'] = airplane;
vehicles['B'] = bus;
vehicles['C'] = car;
vehicles['H'] = horse;
vehicles['T'] = train;

// Use
Vehicle new_vehicle = vehicles[arg];

Depending on how may tables you need/use (that store the same type of object), and the size of the contained objects (Vehicle in this case), this can be a perfectly reasonable alternative to an std::map. If you're creating a lot of tables, or each object is really big, std::map becomes a more reasonable alternative.

When you use an std::map (or unordered_map, etc.) you're using more code to save on data storage. This does the reverse -- but as long as Vehicle is small (say, 4 bytes), one table like the above will typically occupy something like half a kilobyte. It's hard to guess exactly how large the code for std::map is going to be for a specific compiler, but it seems likely that it'll usually be larger than half a kilobyte, so if you're only creating one table like this, std::map may be a net loss.

Of course, if you know that you're only dealing with letters as input, you could reduce the table size quite a bit:

template <class T>
class letter_table { 
    static const int range = 'Z' - 'A';

    T table[range];
public:
    // ...
    T operator[](int index) {
        index -= 'A';
        assert(index<range);
        return table[index];
    }
};

In the example case, this would give a table around 100 bytes -- you can create a pretty fair number of 100-byte tables in the space std::map will normally occupy.

Jerry Coffin
+1 The table solution can of course be used in C as well.
anon
If I was going to use the first method, I'd put `Vehicle vehicles` as a `static` identifier inside a function `getVehicle()`, so that it would be globally accessible and initalized only once but it wouldn't be a global variable clogging up my tubes (and also so I could change the lookup implementation at will without breaking other people's code). Also, if `Vehicle` is an `enum` type, I'd set `feet` to be the first element, so it's value would be zero, so we can avoid the `std::fill_n` (or `memset()` in plain C) call.
Chris Lutz
Looks like an overkill for me.
NB
Also, for super-asinine-pendantic-mode, note that the character set doesn't require the alphabet characters to be consecutive. `'Z' - 'A'` is > 26 in e.g. EBCDIC systems.
Chris Lutz
@Chris Lutz:both excellent points that should be kept in mind when implementing not only this idea, but quite a bit of other code as well.
Jerry Coffin
@Chris Lutz: That's why I used 'Z'-'A' to define the range instead of explicitly coding in `26`. IOW, the code will work with EBCDIC; it'll just produce a *slightly* larger table (something like 3 extra characters, if memory serves).
Jerry Coffin
A typical optimizer will consider a table as a possible implementation of a switch statement. Not so much for the ternary sequence (thoug it's of course possible).
peterchen
`Z` is not required to be larger than `A`
Johannes Schaub - litb
@litb:In theory no, but in reality nobody's ever produced a character set in which it isn't. For this particular issue, EBCDIC is about as strange as it gets...
Jerry Coffin
+1  A: 

Purely practical:

Plus: The ternary sequence is more flexible, and can be used to avoid the limitations of switch, you could use other operators (e.g. <=, >=) or any other tests, including e.g. string comparisons.

x = IsEven(arg) ?  0 : 
    (arg < 0)   ? -1 : 1; // or whatever

Also, if the switch is a performance bottleneck and you have uneven probabilities, you can force the likeliest tests being done first (due to the not evaluated guarantee for the path not chosen).

So-So Unlike a switch statement, order is important (unless you stick to ==). That can be an advantage, but being otherwise similar to switch that might be misleading when the maintainer is unfamiliar with the concept or in a hurry.

Many developers may shy away because they aren't sure about the details (which terms will be evaluated, is the rpecedence of the operators ok?) - However, if your developer pool won't grasp a well-presented example, you might have problems that can't be solved by banning ternary operators.

Minus It isn't as common as switch, thus the optimizer might not treat it the same. Optimizers are know to select the best fit implementation for a switch (table, binary search, comparison sequence, or any combination of this). Optimizers can't reaarange wevaluaiton order, and are less likely to support a table lookup here.

Requires good formatting to be easily recognizable (lining up the '?' and ':') - Sucks when you use tabs.

Aesthetics

I like it for its precision and succinctness, close to mathematical notations. However, that might as well be used against it. It's probably an eyebrow raiser at code reviews, because it is less common and more brittle.

peterchen
+1  A: 

Just for comparison, in C++0x you can have an expression without using the conditional operator or an out-of-line function:

Vehicle new_vehicle = [&]() -> Vehicle {
    if (arg == 'B') return bus;
    if (arg == 'A') return airplane;
    if (arg == 'T') return train;
    if (arg == 'C') return car;
    if (arg == 'H') return horse;
    return feet;
}();

Not really any better, though.

Steve Jessop
+1  A: 

In my opinion what you've done is acceptable due to the simplicity of the example. If you were doing more things with each case this type of construct could rapidly get messy. For that reason I would prefer a switch or even nested if then elses (if there aren't too many cases), formatted as follows:

  if (A) {
      //Do A stuff
  }
  else if (B) {
      //Do B stuff
  }
  else if (C) {
      //Do C stuff
  }
  else {
      //Do default stuff
  }

It's about the readability of the code, which lends itself to the maintainability of the code. I've never been a huge fan fan of the conditional operator, because I don't like to see multiple expressions on a single line. Conditional operators can be difficult to follow when single stepping the code in a debugger. The simpler the code the easier it is to concentrate on what the code is doing.

tkyle
+8  A: 

There's a lot of whitespace around the character constants that makes it a bit hard to read. I'd parenthesize the comparisons: (and maybe move the last value in line.)

Vehicle new_vehicle = (arg == 'B') ? bus      :
                      (arg == 'A') ? airplane :
                      (arg == 'T') ? train    :
                      (arg == 'C') ? car      :
                      (arg == 'H') ? horse    :
                                     feet;

Now it looks great.

aib
I agree with the indentation of `feet` but I'm not a big fan of unnecessary parentheses. But +1 anyway.
Chris Lutz
I'm a huge fan of unnecessary parentheses - even in the unlikely event that I've got the operator precedence right this time, there's no guarantee I'll read it correctly next time ;-)
Steve Jessop
+1 for adding the parens; that makes it much more readable.
ceo
I might even add a /*else*/ comment to the last condition to make it clear what that value represents.
Mark Ransom
+1  A: 

How about:

enum Vehicle { bus = 'B', airplane = 'A', train, car = 'C', horse = 'H', feet = 'F' };
...
new_vehicle = arg;

:-), by the way.

Richard Pennington
+3  A: 

I don't particularly care for it.

It doesn't really buy anything, or make anything more clear, and it's a pretty non-standard usage of the operator.

It seems the primary advantage is that it's somewhat clever. I avoid clever unless there's a pretty good (external) reason to be clever.

kyoryu
+1  A: 

I think its useful for someone who code it, but will be difficult to understand for the reviwer,

"KEEP IT SIMPLE BUDDY"

moon