74

Making some changes in the code I use the next line:

uint a = b == c ? 0 : 1;

Visual Studio shows me this error:

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

But if I use the code:

uint a; 

if (b == c) 
    a = 0; 
else 
    a = 1;

It works correctly without any error or warning. Why?

blahdiblah
  • 33,069
  • 21
  • 98
  • 152
Ruben Aguilar
  • 1,205
  • 11
  • 19
  • 36
    `(b == c) ? 0U : 1U` would work. It's due to implicit conversion which happens with integer literals when being assigned directly to a variable. But when you're using the ternary operator, compiler doesn't do the implicit conversion and only checks if both branches can be converted to the same type. – vgru Mar 09 '17 at 08:22
  • The conditional operator is not an `if...else`, other rules. – Tim Schmelter Mar 09 '17 at 08:23
  • @TimSchmelter, But logically the ?: operator implies a if..else. Would be interesting to see how it translates in the IL. – Thangadurai Mar 09 '17 at 08:27
  • Actually, `... ? 0U : 1` (or `... ? 0 : 1U`) should also work correctly, the compiler should give the `uint` type to the whole expression. – Wiktor Zychla Mar 09 '17 at 08:28
  • I agree with you @Thangadurai. I had suppose that the compiler translate the ternary operator to an if-else when compile it. – Ruben Aguilar Mar 09 '17 at 08:28
  • There is a difference between a constant expression and a non-constant expression. The compiler is able to do more conversions with the former than the latter, and your examples involve the latter when using the ternary operator and the former when not. See marked duplicate and it's marked duplicate for more details. – Peter Duniho Mar 09 '17 at 08:31
  • 1
    @Ruben: both of them will most likely be compiled into the same assembly in the end, but the compiler still has to follow conversion rules. You can basically think of it as `var tmp = (b == c) ? 1 : 0;` (type of `tmp` is inferred), and then `uint a = tmp;` in the next line. – vgru Mar 09 '17 at 08:32
  • There are two options to solve problem of assigning result of `?:` operator: 1) ensure **both** branches will return expected type (e.g. by casting them individually or using suffixes for constants) 2) ensure what **result** of `?:` is of expected type by casting it: `var a = (uint)(condition ? type : type)` (error message clearly states this cast is required). – Sinatr Mar 09 '17 at 09:01
  • Actually the assembly looks different. The ternary operator does not have a jump at all. [godbolt.org](https://godbolt.org/g/GX9WSk) Sadly I'm not fluent enough in assembly to actually understand the ´movzx´ – sbecker Mar 09 '17 at 13:17
  • 11
    A better way to avoid this problem is to not use uint in the first place. Why are you using uint? This is a bad code smell in C#. uint is there for interoperability with C and COM libraries that use uint. – Eric Lippert Mar 09 '17 at 16:43
  • Another solution would be: `uint a = Convert.ToUInt32(b != c);` – xehpuk Mar 09 '17 at 18:01
  • 5
    @EricLippert why is uint a bad code smell? If you have a value that should never be negative and you need a higher upper limit but don't want to allocate more memory, then wouldn't using this be considered an optimisation? Implementing your own check to keep from falling below 0 would just introduce unnecessary logic. Just curious is all ... – Jake Millington Mar 09 '17 at 23:38
  • 4
    @JakeMillington: I said why it is a bad code smell. It is there for *interoperability with COM*. You'll note that `string.Length` is never negative, but is not a uint. Neither is `List.Count`. Being non-negative is insufficient reason to use `uint`. I submit to you that an *optimization* should be justified by *data*; is there a program in the marketplace whose success or failure is gated on the choice of whether to use `uint` or `long`? – Eric Lippert Mar 10 '17 at 00:17
  • 6
    @JakeMillington: Moreover, if you have a quantity that is routinely over two billion, then *use a long for heaven's sake*. Do you really want to spend even a single minute debugging the problem when the quantity you thought would always be less than four billion is suddenly five billion, and the uint wraps? – Eric Lippert Mar 10 '17 at 00:18
  • 11
    @JakeMillington: But wait, there's more. Let's suppose we have two quantities that are logically never negative. The size of a string, say. OK, I mote it so: `string.Length` is now `uint`. Now please write for me correctly the code that takes two strings and gives me the difference in their lengths. How often do you think you'd forget to add the cast to int? How long do you think it would take for that bug to cause a serious failure? And now we'd have a program whose failure in the marketplace is due to use of uint! **uints do not match the mathematical domain in which we routinely work.** – Eric Lippert Mar 10 '17 at 00:21
  • 2
    @EricLippert Point taken. I was under the impression that allocating the extra memory for a long was a bad thing if a larger portion of it would stay untouched. Thanks for that explanation. Still learning here .... – Jake Millington Mar 10 '17 at 02:20
  • 8
    @JakeMillington: A long is eight bytes. An int is four. You have at a minimum TWO BILLION bytes of address space. Do you have more than say a hundred million longs? If so, start worrying about it then. You are a memory billionaire; don't worry about pennies. – Eric Lippert Mar 10 '17 at 06:19

3 Answers3

87

Why can't I use uint a = b == c ? 0 : 1;?

The type of the expression b == c ? 0 : 1 is int. As shown in this table, there is no implicit conversion from int to uint, so this is not allowed.

Why can I use a = 0?

Because there is special treatment of numeric types when the value is a constant expression.

From section 6.1.9 of the C# specification:

  • 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.

  • A constant expression of type long can be converted to type ulong, provided the value of the constant expression is not negative.

As indicated in the first bullet a = 0 and a = 1 are both allowed because 0 and 1 are constant expressions and are valid uint values. Basically what this boils down to is that the compiler can easily determine at compile time that these conversions are valid, so it allows them.

Incidentally, if the b == c part of your first example were changed to a constant expression (e.g. true), then the whole conditional operator expression would be a constant expression and the code would compile.

Community
  • 1
  • 1
JLRishe
  • 99,490
  • 19
  • 131
  • 169
26

If b==c were a constant expression then the whole conditional operator would be considered a constant expression and so, then, the rule allowing constant expressions of type int to be converted to other int types would apply and it would compile.

Obviously, b==c is not a constant expression and so the result of the conditional operator cannot be known until runtime and so the exemption that allows an implicit conversion of ints to uint (for constant expressions) does not apply.

In your if/else variant, both of the actual assignments are constant expressions.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448
  • 4
    This explains why `uint a = true ? 0 : 1` *does* compile. The gist of it is that implicit conversion only applies for constant expressions. – haim770 Mar 09 '17 at 08:31
  • 1
    @haim770 - that's why I decided to answer. Groo's comment, whilst being mostly sufficient, implied that the conditional operator could never allow this to work. – Damien_The_Unbeliever Mar 09 '17 at 08:32
10

You should use literals to make your code work correctly like this:

uint a = b == c ? 0U : 1U;
teo van kot
  • 12,350
  • 10
  • 38
  • 70