The only predefined operator in c and c++ (I'm not sure about other languages) which takes three operands.
foo == bar ? foo = baz : bar = baz;
The above code first executes foo == bar. If it evaluates to true, the first statement, foo = baz is executed; if it evaluates to false, bar = baz is executed.

In my humble opinion, these make your code look a whole lot more 'leet than if and else statements.

Warning: Strangely enough, return () statements don't seem to work with this operator, even though I can't find any documentation that tells me why. Anybody who tells me why (or shows me where to find out) gets a cookie.


Oh, and apparently my last paragraph wasn't clear. Here's what I mean:

foo == bar ? return (foo) : return (baz);

Doesn't work. Of course,

return (foo == bar ? foo : baz);

Does work....But I prefer the non-working version. Oh well.
The conditional operator is important when you want your programs to utilize correct grammar. Most prominently, when differentiatiating between plural and singular forms. So rather than "There are 1 values greater than 10," or "There is 5 value less than 20," you can use conditional operators to keep you from sounding like an idiot:
(value == 1 ? "is" : "are")
(value == 1 ? "value" : "values")
So when "value" (whatever it is) is equivalent to one (one apple, one truck, one SCV), the singular form will be chosen; when it is more (or less) than one (3 accidents, 25 bills, 0 dollars), the plural form will print.

Correctly, now: There is one value; there are 5 values.
The '?:' syntax of C like languages comes as a borrowed concept from functional languages (such as Lisp, Scheme, and ML).

In Lisp everything returns something. This is even true of the 'if' function. With common lisp, 'if' has the structure:
if test then [else] => result
In an actual lisp session, this would look like:

(if t 1 2) => 1
(if nil 1 2) => 2
In the above example, the return value of the if is displayed to the right. Thats correct, return value of the if statement. Within procedural languages, the concept of a return value for every instruction and block of code is a bit foreign. Blocks may certainly return a value, but are not legal on the right hand side of assignment operator.

And yet, the return value from an if is such a useful idea, especially when it is properly understood by all parties and not abused to make the code difficult to read. Consider the following code from perl (just happens to be what I'm thinking in now)

# var         does it exist               use it       default
$opt_test  = (defined $options{'test'})?  $options{'test'}  :0;
$opt_log   = (defined $options{'log'})?   $options{'log'}   :1;
$opt_force = (defined $options{'force'})? $options{'force'} :0;
$opt_debug = (defined $options{'debug'})? $options{'debug'} :0;

The separation between statements and expressions is an important one within procedural languages and its offspring of object oriented languages (most of which come from a procedural background). Most notably is the rigidity with how these languages handle types compared to that of functional languages. The type of an expression is known at compile time and fairly easy to figure out. Consider the difficulty in attempting to figure out the 'type' of an anonymous block of code (which is itself a 'statement') or a statement.

In most cases, while it would be 'neat' to have a 'while' loop or a 'for' loop (or any other structure) return a value, this is rarely needed (or desired). Instead, it would add needless complexity to the language that would rarely be used, and in essence, violates the ideals of a procedural language. (The only procedural language to do such a thing was a variant of Algol, however it applied only to the 'if' keyword and its positioning relative to parentheses as an indicator if it was to be an expression or statement. Realize that Algol did a number of other 'interesting' things to its syntax (to say the least).) It turns out that separating the expression 'if' from the control structure 'if' makes it easier on the complier and leads to less likelyhood of confusion on the part of the programmer (such as the confusion with a '=' instead of a '==' within an expression in C).

The one exception to this, is the 'if' expression which has the syntax of '?:'. Hidden within other expressions or simply shorthand for a conditional. While quite simple, picture the 'if' form of the following:

foo = (bar?bar:1)/(qux?qux:1);
If 'bar' is true (not 0), then use it, otherwise use '1'. This is then divided by qux, which has the same applied to it.

The '?:' expression is found in most any language that is derived from BCPL (the predecessor of B, the predecessor of C) or one of its descendants. From the reference manual found at:
http://cm.bell-labs.com/cm/cs/who/dmr/bcpl.html (page 15) dated July 21, 1967.

5.5 Conditional Expressions

Syntactic form: E1 -> E2, E3
E1, E2 and E3 may be any logical expressions or expressions of greater binding power. E2 and E3 may, in addition be conditional expressions.
Semantics:
The value of the conditional expression E1 -> E2, E3 is the Rvalue of E2 or E3 depending on whether the value of E1 represents true or false respectively. In either case only one alternative is evaluated. If the value of E1 does not represent either true or false than the result of the conditional expression is undefined.

While it was never fully implemented, CPL (the language that lead to BCPL) had a similar structure. The language definition can be found in pages 136-137 of "The main features of CPL" by Barron, D. W., Buxton, J. N., Hartley, D. F., Nixon, E., Strachey, C. as part of Computer Journal, volume 6 (1963) which is online at http://www3.oup.co.uk/computer_journal/hdb/Volume_06/Issue_02/ (many thanks to jrn who dug up the refrence).

The '?:' syntax was first seen as part of B:
(from http://cm.bell-labs.com/cm/cs/who/dmr/kbman.html)

rvalue ::=
    ( rvalue )
    lvalue
    constant
    lvalue assign rvalue
    inc-dec lvalue
    lvalue inc-dec
    unary rvalue
    & lvalue
    rvalue binary rvalue
    rvalue ? rvalue : rvalue
    rvalue ( {rvalue {, rvalue} } )

An interesting feature of C++ is that the result of the ?: operator is an lvalue (a value that can be assigned to) if the second and third operands are both lvalues. This means for example that an expression with the ?: operator can appear on the left side of an assignment operator.

For example, (e==5?a:b) = 10; is almost equivalent to:

if (e == 5)
  a = 10;
else
  b = 10;

The difference is that the first statement is an expression that has a value (10), but the second isn't an expression.

Below is a little program that shows some of the possibilities. I have tested it with Borland C++ 5.5 for Win32 and gcc/g++ version 2.95.2.

#include <iostream.h>

int main(void)
{
  int a=1, b=2, c=3, d=4, e=5, *f=&a;
  (e==5?a:b) = 10;
  (e==7?c:d) = 11;
  cout << "a: " << a << " b: " << b << " c: " << c << " d: " << d << endl;
  e = (e==9?a:b) = 12;
  cout << "a: " << a << " b: " << b << " c: " << c << " d: " << d;
  cout << " e: " << e << endl;
  (e==b?(e==3?*f:b):c) ++;
  cout << "a: " << a << " b: " << b << " c: " << c << " d: " << d << endl;
  f = &((e==b?(e==3?*f:b):c));
}
/* Expected output:
a: 10 b: 2 c: 3 d: 11
a: 10 b: 12 c: 3 d: 11 e: 12
a: 10 b: 13 c: 3 d: 11
*/


A little remark, thanks to m_turner: It is possible to achieve a similar effect in C by writing *(e==5?&a:&b) = 10;. Here we use the ?: operator to choose between the addresses of the variables. Then we apply the dereference operator (*) to the expression, which produces an lvalue that can be assigned to.

There is something which has been ignored by all the WU's so far -- the type of the expression. In C++ all expressions are statically typed, so the compiler must know what all the types of the expressions are. For the simple <grin> case, the following rules are obeyed for ( T ? A : B ):

  1. If A and B are of the same type, then the entire expression has that type.
  2. If A can be converted to B, it does so.
  3. Then the other way is considered.

This is the simple case because we have ignored l/r-values, and const/volatile cases. It happens that there are some more conditions for those cases. Language lawyers may refer to The Good Book for more details.

Why is this important? Because it allows us to get the type of an expression without having to evaluate it. Suppose we have the following functions:

template <typename T>
std::string get_type_name(T x) {return "unknown";}

template <>
std::string get_type_name<int>(int x) {return "int";}

int int_returning_function() {/* does something */}

Now if we call get_type_name(int_returning_function()) we will get a std::string containing "int" as expected. But in doing this, int_returning_function will be evaluated; that is, called, and side-effects may then occur. What if this is part of our debugging infrastructure? We don't want the existence of debugging statements to change the execution of the program.

Conditional operator to the rescue!

It's easiest to demonstrate through code:

template <typename T>
class encoded_type {};

template <typename T>
encoded_type<T> encode_type(const T &) { // note that we don't actually need the parameter's name -- just the type
    return encoded_type<T>();
}

class silly_base {
    // only purpose is to be able to convert into arbitrary encoded_type's
    template <typename T>
    operator encoded_type<T>() const {
        return encoded_type<T>();
    }
}

// macro to make all this happen:
#define ENCODE_TYPE(type) \
    (true ? silly_base() : encode_type(type))

// get_type_name, redux
template <typename T>
std::string get_type_name(encoded_type<T>) { // again, we don't care about the parameter, just the type
    return "unknown";
}

template <>
std::string get_type_name<int>(encoded_type<int>) {
    return "int";
}

Usage: get_type_name(ENCODE_TYPE(int_returning_function()))

We use a macro here, ironically because of the classic problems that plague the naive implementation of max and min as macros -- the arguments are not evaluated at bind time, but only as they are used. But in our case, the branch of the conditional operator that contains our int_returning_function, is actually never taken, because the test is always true! At the same time, because silly_base can be converted to any encoded_type<T>, whilst not the other way round, the entire expression will be of type encoded_type<T>, where T is the type of the expression we wanted to know about.

Look through the code a few more times, and convince yourself that this works as intended. It's not obvious, and it combines several "shortcomings" of C++ to get a solution. Vive le C++!

Log in or registerto write something here or to contact authors.