views:

176

answers:

4

Possible Duplicate:
Conditional operator cannot cast implicitly?

I have run into a peculiar situation and want to know why I have to do it. I'm using .net 3.5.

This works:

short foo;

if (isValid)
    foo = -1;
else
    foo = getFoo();

This does not work:

short foo;
foo = isValid ? -1 : getFoo();

I have to typecast -1:

short foo;
foo = isValid ? (short)-1 : getFoo();

I'm wanting to know what the ternary expression does differently? It considers the -1 to be an int that needs to be cast into a short. But why?

+6  A: 

Because -1 by default is an integer. It's a lot safer for the compiler to tell you that you have to explicitly tell it what to do, than it is for the compiler to make an assumption as to what you want done.

You're example is pretty straight forward. With a little extra work, the compiler could obviously see that you want -1 to be a short. There are all the edge cases that go along with implicit conversion that aren't so simple though. If you add a rule for the compiler to make an assumption about what you want, you have to apply it to every case not just one and that is where it gets difficult.

As a note you should check out Eric Lippert's blog as I know that he covers why the compiler doesn't make such assumptions.

Kevin
+1  A: 

A conditional operator applied to an int and a short is of type int; the compiler will not infer the expression's type from the type you assign it to.

You cannot implicitly cast this short expression to an int.

SLaks
A: 

The conditional operator imposes that both possible result expressions be of the same type. In this case the left side is an int and the right side is a short returned by getPoo method. Since it's always safe to convert a short to an int the compiler chooses that the result of the operation will be an int.

So the result will be the assignment of an int to a short and that's why you need to explicitly cast it to a short.

If you explicitly use an if/else approach you will be assigning a literal integer to a short which allows the compiler to verify that the literal integer is safely assigned to a short without the need of a explicit cast.

For an inside explanation take a look at:

Cast operators do not obey the distributive law

João Angelo
+10  A: 

A few things.

First off, the conditional operator is a ternary operator, not a teriary operator.

Second, I note that in your code samples the two code samples which are intended to be equivalent are not:

short foo; 
if (isValid) 
    foo = -1; 
else 
   getFoo(); 

is not the same as

short foo = isValid ? (short)-1 : getFoo();

The former leaves foo unassigned if isValid is false. The latter assigns foo regardless of the value of isValid.

I assume that you meant

short foo; 
if (isValid) 
    foo = -1; 
else 
    foo = getFoo(); 

and that furthermore, getFoo() returns short.

The question is why the conversion in the conditional operator without the type cast is illegal but in the consequence of the if statement is legal.

It is legal in the if statement because section 6.1.9 of the specification states:

A constant-expression of type int can be converted to type sbyte, byte, short, ushort, uint, or ulong, provided the value of the constant-expression is within the range of the destination type.

-1 is a constant expression of type int that is in the range of short, so it can be converted to short implicitly.

So why is the conditional expression form bogus?

The first thing we have to establish clearly is the rule that the type of the conditional expression is determined from its contents, not from its context. The type of the expression on the right side of an assignment does not depend on what it is being assigned to! Suppose you had

short M(short x){...}
int M(int x){...}

short y = M(-1);

I don't think you'd expect overload resolution to say "well, I'd normally pick M(int) because -1 is an int, but no, I'll pick M(short) instead because otherwise the assignment won't work." Overload resolution doesn't know anything about where the result is going. It's job is to work out what the right overload is based on the arguments given, not based on the context of the call.

Determining the type of the conditional expression works the same way. We don't look at the type its going to, we look at the types that are in the expression.

OK, so we have established that the fact that this is being assigned to short is irrelevant for determining the type of the expression. But that still leaves the question "Why is the type of the conditional expression int rather than short?"

That is a very good question. Let's go to the spec.

The second and third operands, x and y, of the ?: operator control the type of the conditional expression.

If has type X and y has type Y then:

If an implicit conversion exists from X to Y, but not from Y to X, then Y is the type of the conditional expression.

If an implicit conversion exists from Y to X, but not from X to Y, then X is the type of the conditional expression.

Otherwise, no expression type can be determined, and a compile-time error occurs.

In this case the operands both have a type. (The verbiage in there about "if x has a type..." is for the case where you have null or a lambda in there; those don't have types!) The first operand is of type int, the second is of type short.

An implicit conversion exists from short to int, but not from int to short. Therefore the type of the conditional expression is int, which cannot be assigned to short.

Now, one could say that this algorithm is not as good as it could be. We could greatly complicate the algorithm to deal with all the cases where there were two possible "candidate" types -- in this case, int and short are both plausible candidates because both branches are convertible to both int and short when considered as specific expressions, rather than simply as having types. We could say in that case that the smaller of the two types was the preferred type.

(Sometimes in C# we say that the more general of two types is the better type, but in this case you would want us to pick the more specific. The language is not consistent in this particular design aspect, unfortunately; I personally would rather we always choose the more specific, but there are type inference scenarios where that would be a breaking change now.)

I considered doing that back in 2006. When designing the behaviour of how LINQ deals with situations where there are multiple types to choose from and one must be picked as "the best" we noticed that the conditional operator already had to solve this problem, and that furthermore, in C# 2 it was not actually implemented according to spec. There was a long debate about this and we ended up making some minor changes to the specification for the conditional operator to bring it more into line with its implemented (and desired) behaviour. However we decided to not take the larger breaking change of tweaking the algorithm to use the smaller of two possible types when there were several to choose from.

For some musings on this problem, see my posts from 2006 on it:

http://blogs.msdn.com/b/ericlippert/archive/2006/05/24/type-inference-woes-part-one.aspx

http://blogs.msdn.com/b/ericlippert/archive/2006/05/26/type-inference-woes-part-two.aspx

http://blogs.msdn.com/b/ericlippert/archive/2006/05/30/type-inference-woes-part-three.aspx

Eric Lippert
Thank you for the insight. You were correct in that I meant foo = getFoo();
jsmith