31

This works:

short value;
value = 10 > 4 ? 5 : 10;

This works:

short value;
value = "test" == "test" ? 5 : 10;

This doesn't work:

short value;
string str = "test";
value = "test" == str ? 5 : 10;

Neither does this:

short value;
string str = "test";
value = "test".Equals(str) ? 5 : 10;

The last two cases I am getting the following error:

Cannot implicitly convert type 'int' to 'short'.
An explicit conversion exists (are you missing a cast?)

Why do I have to cast on the last two cases and not on the two first cases?

vgru
  • 49,838
  • 16
  • 120
  • 201
Vitor Freitas
  • 3,550
  • 1
  • 24
  • 35
  • @Tyler: indeed, sorry removed the close vote. The duplicate one I gave was not specific. http://stackoverflow.com/questions/3678792/are-string-equals-and-operator-really-same?rq=1 States the difference between compile-time (==) and run-time (equals). Also 10 > 4 will be optimized out by the compiler where a variable is most of the time checked run-time (of not a constant) – RvdK Aug 01 '14 at 14:51
  • 2
    possible duplicate of [No implicit int -> short conversion in ternary statement](http://stackoverflow.com/questions/1672774/no-implicit-int-short-conversion-in-ternary-statement) – ahruss Aug 01 '14 at 15:21

4 Answers4

45
short value;
value = 10 > 4 ? 5 : 10;             //1
value = "test" == "test" ? 5 : 10;   //2
string str = "test";
value = "test" == str ? 5 : 10;      //3
value = "test".Equals(str) ? 5 : 10; //4

The last two ternary expressions (3,4) cannot be resolved to a constant at compile time. Thus the compiler treats the 5 and 10 as int literals, and the type of the entire ternary expression is int. To convert from an int to a short requires an explicit cast.

The first two ternary expressions (1,2) can be resolved to a constant at compile time. The constant value is an int, but the compiler knows it fits in a short, and thus does not require any casting.

For fun, try this:

value = "test" == "test" ? 5 : (int)short.MaxValue + 1;
Timothy Shields
  • 75,459
  • 18
  • 120
  • 173
  • 5
    For reference: this behavior implemented by the compiler is called [Constant Folding](http://en.wikipedia.org/wiki/Constant_folding) – Bas Aug 01 '14 at 14:48
  • 1
    @Groo -- You know it took less than 10 mins for the correct answer. – Hogan Aug 01 '14 at 14:50
  • 1
    [Here's the MS Documentation](http://msdn.microsoft.com/en-us/library/y5b434w4.aspx) that says this can happen. "A constant expression of type int can be converted to sbyte, byte, short, ushort, uint, or ulong, provided the value of the constant expression is within the range of the destination type." But this doesn't really explain why the compiler can't figure out that both 5 and 10 can be shorts, other than the obvious "because it can't". – ahruss Aug 01 '14 at 15:04
  • @ahruss The compiler absolutely can figure out that both 5 and 10 can be `short`. The problem is, if `E` is the expression `B ? X : Y` where `B` is a non-constant `bool` expression, then `E` is always interpreted as a non-constant expression, regardless of whether `X` and `Y` are constant or not. If `X` is `5` and `Y` is `10`, the types of those expressions are both `int`. So `E` is non-constant and has type `int`. A non-constant `int` cannot be implicitly cast to a `short`. – Timothy Shields Aug 01 '14 at 15:26
  • @ahruss (continued) The compiler reasons by constant versus non-constant - it doesn't keep bounds for non-constant variables. The reasoning you are suggesting would require the compiler to say `B ? 5 : 10` is always going to be a value in `[5, 10]`. – Timothy Shields Aug 01 '14 at 15:27
  • @TimothyShields It could just as easily look at the form of the expression and figure out the required types: (bool) ? (short) : (short). I know expressions are supposed to have types on their own, but C# is not even consistent with that. Lambdas infer their type from usage, for example. So what's the downside to figuring out the type for integer literals based on the expected type of the expression? (There's a similar problem with `null` in similar situations this could also resolve.) – ahruss Aug 01 '14 at 15:35
  • @ahruss Type inference only flows up the abstract syntax tree. It's not a violation of that statement when the compiler allows `short x = ;` or `f()` where the signature is `void f(short x)` - as long as the `` fits in a `short`. When I write `short x = 5;`, the `5` is still an `int` literal, not a `short` literal. You could imagine a compiler that can do type inference in both directions on the abstract syntax tree - for example `if (E) { ... }` would imply that `E` should be a `bool` - but the C# compiler doesn't do that. – Timothy Shields Aug 01 '14 at 15:45
  • @TimothyShields It only flows up... except when it doesn't. It flows down for lambdas. – ahruss Aug 01 '14 at 15:53
  • @ahruss No, it doesn't. Lambda expressions do not derive their type from things above them in the AST. In fact, *lambda expressions don't have a type at all* in the AST. I think this comment thread is getting too long. Might not be a bad idea to ask a standalone question about the lambda expression typing. – Timothy Shields Aug 01 '14 at 16:01
4

You need a cast to make the two last examples work

value = (short)("test" == str ? 5 : 10);

Why dont't you need it in the first two?

Because the first two are compile-time constants. The compiler is able to translate 10 > 4 ? 5 : 10 to true ? 5 : 10, then to just 5

So when you write

value = 10 > 4 ? 5 : 10;

It's effectively the same as

value = 5;

which compiles because the compiler is allowed to implicitly cast constants if they are in the allowed range.

Conversely, "test" == str ? 5 : 10; is not a compile time constant, so the compile is not allowed to implcitly cast it. You need to make an explicit cast yoursef.

Falanwe
  • 4,636
  • 22
  • 37
  • 1
    Entirely correct. For an "academic" difference between `(short)("test" == str ? 5 : 10)` and `"test" == str ? (short)5 : (short)10`, see my answer. – Jeppe Stig Nielsen Aug 04 '14 at 00:48
3

This is defined by the C# Language Specification, of course.

The key thing to be aware of is that there are two kinds of conversions from int to short. One is an explicit conversion which always applies but which requires you to write (short) explicitly before the int expression. The other one is an implicit constant expression conversion which only applies when (a) the int expression is a compile-time constant and (b) the value of this compile-time expression is within the range of a short, that is -32768 through 32767.

A literal like 5, 10 or 4 has type int in C# (that goes for any integer literal which is between -2147483648 and 2147483647 and not followed by a symbol L, U or similar). So if we look at the right-hand sides of all your assignments, they are clearly int expressions, not short.

In the case 10 > 4 ? 5 : 10, since 10 and 4 are compile-time constants, this is the same as true ? 5 : 10 because the > operator between ints is built-in and will result in a constant when the operands are constants. And in the same way true ? 5 : 10 gives 5 because all three operands are constants, and ?: is classified a constant itself in that case. So it really says:

short value = 5;

where the "5" is a compile-time constant. Hence it is checked at compile-time if the int 5 is within the range (it does not matter with the 10, it could be 999999), and since that is the case, the implicit constant expression conversion applies, and it is legal.

Note that you can do the same with:

const int huge = 10;
const int tiny = 4;
const int significant = 5;
const int unimporatnt = 10;
short value;
value = huge > tiny ? significant : unimportant;

as long as all the operands are const variables (pun?).

Now, if I managed to make the explanation clear, you will also know by now that the obstacle preventing value = "test" == str ? 5 : 10; from working is that you did not mark the str local as const. Do that, and it will be allowed.

With the Equals call, things are a bit worse. The result of a call to Equals is never considered a compile-time constant (and I don't think it is "optimized" away, for example "same".Equals("same") will actually call the method at run-time). Same thing would happen with (10).Equals(4) or (10).CompareTo(4) > 0 and so on, so strings are not special in this respect.


Most likely you know already that when

short value = cond ? 5 : 10;

is not allowed because cond is not a compile-time constant, you just use the explicit conversion instead, so write:

short value = cond ? (short)5 : (short)10;

or:

short value = (short)(cond ? 5 : 10);

Technically, they are not identical, since the first one has no narrowing conversion at run-time (the expressions (short)5 and (short)10 are literals of type short), while the last one has to convert an int to short at run-time (which is of course cheaper than incredibly cheap).


The other (non-deleted!) answers are correct, this is just bonus information.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
2

The thing is that 10 > 4 ? 5 : 10; is actually converted to a constant at compile time before any type casting is needed. Meaning the compiler realized that the turnary statement can actually be reduced to a constant even before any implicit type casting is required for the compilation. So in other words that expression is the same as:

value = 5;

In the last two statements, that is not true since you are using a variable to hold the values for you and not a constant. The compiler doesn't check the actual value of the variable to see if it can reduce the expression to a constant. So you actually need the casting.

Farhad Alizadeh Noori
  • 2,276
  • 17
  • 22