There is quite a lot of C++ that is complicated, difficult to understand, easy to misuse, or all of the above. Of course people love to write about the complexities of something like initialization (guilty). Lately it’s apparently become in vogue to over-the-top fear-monger about reference semantic types (something I intend to write about in the future). But there’s one part of the language that is quite complicated yet just doesn’t quite get the love it deserves: the conditional operator, ?: (it’s so unloved that it’s quite common to misname it as the ternary operator).

It takes over a full page of the standard to explain what it does:

[expr.cond] from the latest working draft, N4741

The complexity comes from all sorts of things you can do with it. You can compare expressions of different value categories, of different types. One or both can be void ! You can have bitfields? You can, of course, throw . We have bullets, we have sub-bullets, we have special cases for things like pointers to members and nullptr_t . There’s something for everyone in this section.

Now despite the wordiness here, cond ? a : b basically does what you’d expect and want it to do. Let’s say we have some Regular type T (I’m going to ignore cross-type conversions and void for the purposes of this post), there are six kinds of expressions as the second or third operand — the Cartesian product of the value categories (3: lvalue, xvalue, prvalue) and the cv-qualifiers (2: non-const and const, I’m also ignoring volatile for the purposes of this post). That makes 15 different potential pairings for a conditional expression to consider what the resulting type and value category should be. You don’t have to memorize what all 15 result in though — there are really only three important ones:

If both are glvalues and have the same type and value category (e.g. both are T& or both are T const&& ), the result is that same type and value category.

or both are ), the result is that same type and value category. If both are glvalues, but one’s type is more const than the other, the more const one is the type (e.g. if a is T& and b is T const& , then true ? a : b is T const& ).

is and is , then is ). Otherwise, it’s a prvalue (just T ).

A different way of looking at this — if it’s safe to get a reference, you get the most useful reference back. Otherwise, you get a value. A pretty handy feature!

Note that this isn’t 100% full-proof, you can definitely get a dangling reference out of it:

struct S { int i; };

decltype(auto) foo(int&& j) {

return true ? S{4}.i : std::move(j);

}