display | more...

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