The (?,:) operator, as in –
int a = (b>5)? 6 : 7 ;
is commonly referred to as the Ternary Operator. (It is a common abuse of terminology: a ternary operator is one that operates on three arguments. This particular ternary operator is the Conditional Expression operator.) It has several surprising attributes.
First – did you know that it can result in an lvalue? This is perfectly valid c++ code:
int a, b, c = 5; ((c>4) ? a : b ) = 42;
I can imagine some scenarios where this could make code more succinct, but for the most part (quoting my friend Liron Leist here) this is just the kind of code that makes compilers scratch their heads and go open a c++ book.
Here’s a second gotcha that bit me a while ago, and only recently did I feel I actually understand it. Lo the following:
class Pet {}; class Dog : public Pet {}; class Cat : public Pet {}; Pet* pPet = NULL; // This compiles perfectly: if( IsSocialGuy() ) pPet = new Dog; else pPet = new Cat; // While this doesn't: pPet = IsSocialGuy() ? new Dog : new Cat;
Kinda surprising. The compiler feedback even more so:
error C2446: ‘:’ : no conversion from ‘Cat *’ to ‘Dog *’
Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast
Huh? cast Cat* to Dog*? Who ordered that? MSDN isn’t much help either:
If both expressions are of pointer types (…) , pointer conversions are performed to convert them to a common type.
Ok, what? Seems both of the expression alternatives needs to be evaluated as expressions – why must they result in identical types? Even worse: suppose you can accept this as a c++ quirk – isn’t it painfully obvious what casting has to be done to convert both Cat* and Dog* to a common type (namely, Pet*)?
Well – thank the universe for Eric Lippert – no, it is not obvious one bit. The code continues:
void PutInMicrowave(Dog* pPet) { cout << _T("Woof? Splat."); } void PutInMicrowave(Cat* pPet) { cout << _T("Meaw? Splat."); } // How would you suggest compiling this call? PutInMicrowave( IsThai() ? new Cat : new Dog);
If the types of the two expression alternatives do not coincide, the compiler faces the risk of being unable to resolve overloads. As Eric beautifully put it, ‘We need to be able to work out the type of an expression without knowing what it is being assigned to. Type information flows out of an expression, not into an expression.’
Elsewhere, Eric lays out the details of the logic behind ‘pointer conversions are performed to convert them to a common type’. This is, however, in C# context (as was his StackOverflow reply) – I can only speculate that the C++ semantic logic is similar, but can’t find any authoritative links (suggestions are welcome!)
Thanks for your insights. I just happend to get a “error C2446: ‘:’ : no conversion from ‘Cat *’ to ‘Dog *’” and googled it. The lvalue result was a nice bonus, I’d never have thought of that.
You can easily solve it with clone().
Ah sorry I didn’t you are using the 2nd way to clone a pet… Microwave.
I got that error a couple of days ago. I suppose the ternary operator uses the second operator type as the main type, so if the third operator cannot be casted (as in the example), you are doomed.
Of course, you can use inheritance, but make sure the compiler will get the second operator as a pointer to the base class (that is, Pet)
PutInMicrowave( IsThai() ? (Pet*) new Cat : (Pet*) new Dog);
As far as i can tell such a direct preference (3rd argument is the ‘main’ one) does not exist – and doesn’t seem reasonable. Quoting Eric Lippert’s link:
“Let X and Y be the types of the second and third operands. Then,
* If X and Y are the same type, then this is the type of the conditional expression.
* Otherwise, if an implicit conversion exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.
* Otherwise, if an implicit conversion exists from Y to X, but not from X to Y, then X is the type of the conditional expression.
* Otherwise, no expression type can be determined, and a compile-time error occurs. ”
Admittedly that’s C#, but as far as i could test this applies to C++ as well. Could you post a code snippet that demonstrates when the 3rd argument determines the type?