Why do these two code snippets have the same effect?
The type of a conditional expression does not depend on whether the condition is true or not.
decltype(b<a?a:b)
is the type of the expression b<a?a:b
, which is always the same.
It is not either decltype(a)
or decltype(b)
depending on the value of b<a
.
Note that the expression you give to decltype
is never evaluated - only its type is determined, and it is determined at compile time.
Somewhat informally:
- if
a
can be converted to the type ofb
, the expression has the same type asb
- if
b
can be converted to the type ofa
, the expression has the same type asa
- otherwise, the expression is ill-typed.
(There's also a trtuckload of nitty-gritty details about standard conversions and yada-yada involved, but this is the gist of it.)
For instance, this will not compile becuse there are no valid conversions that could give notgood
a type:
int main()
{
decltype(true ? "hello" : 123.4) notgood;
}
while this will compile, and run, and be well-defined, because the invalid dereference is never evaluated:
int main()
{
decltype(*(int*)0 + 1)` x = 0;
return x;
}
Because the type returned by a ternary operator is decided according the types of the second and third arguments, not according the value of the first.
You can verify this with the following code
#include <type_traits>
int main ()
{
auto x = true ? 1 : 2l;
static_assert( std::is_same<decltype(x), long>::value, "!" );
}
Isn't important that true ? 1 : 2l
return ever 1
; the ternary operator return a common type between 1
(int
) and 2l
(long
). That is long
.
In other words: there isn't (at the moment) a constexpr
ternary operator.
The rules for determining the type of a conditional expression are described here.
As the others have already said, the key is to realize that the expression
E1 ? E2 : E3
taken as a whole is an expression, and an expression has a single type (and value category) determined at compile time. It can't change type depending on which path is taken, because in general that isn't known until runtime.
So, the rules are pretty extensive. Skipping the void
and bit-field special cases, it works something like this:
- If either E2 or E3 has type
void
... assume they don't. - Otherwise, if E2 or E3 are glvalue bit-fields ... assume they aren't.
Otherwise, if E2 and E3 have different types, at least one of which is a (possibly cv-qualified) class type ...
OK, this one might be true. We don't yet know the types of E1 and E2, but it's certainly plausible.
If this case applies, there's a whole list of steps it must follow, and if it succeeds then it figured out either how to implicitly convert E1 to E2, or E2 to E1. Whichever, we pick up at the next step with two subexpressions of the same type.
If E2 and E3 are glvalues of the same type and the same value category, then the result has the same type and value category
That is, if our original T1 and T2 are the same, then the type of the expression is just that. This is the simplest case.
If they're different types but the compiler figured out an implicit conversion in step 3 above, we're looking at either
(T1,T1)
or(T2,T2)
and the same applies.Otherwise, the result is a prvalue [roughly - anonymous temporary]. If E2 and E3 do not have the same type, and either has (possibly cv-qualified) class type, overload resolution is performed using the built-in candidates below to attempt to convert the operands to built-in types ... the converted operands are used in place of the original operands for step 6
Maybe they're classes with conversion operators like
operator bool
- then we haven't found another answer, so we'll do the conversion tobool
and keep going.The lvalue-to-rvalue, array-to-pointer, and function-to-pointer conversions are applied to the second and third operands
These are a bunch of standard implicit conversions just to make both sides as similar as possible.
Then,
If both E2 and E3 now have the same type, the result is a prvalue of that type
We managed to massage both sides to have the same type, hooray!
If both E2 and E3 have arithmetic or enumeration type: the usual arithmetic conversions are applied to bring them to common type, and that type is the result
The usual arithmetic conversions are what allow you to add, say, and
int
and adouble
and get some result. This will work the same way.etc. etc.