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!)