2

I'm little bit confused. During development of one function that based on predefined parameters, pass to sprintf function exact parameters needed based on their type, I found really strange behaviour ( something like "This is %f %d example", typeFloat, typeInt ).

Please take a look at following stripped working code:

struct Param {
 enum { typeInt,    typeFloat   } paramType;
 union {
    float f;
    int i;
 };
};

int _tmain(int argc, _TCHAR* argv[])
{
 Param p;
 p.paramType = Param::typeInt;
 p.i = -10;

 char chOut[256];
 printf( "Number is %d\n", p.paramType == Param::typeInt ? p.i : p.f );
 printf( "Number is %f\n", p.paramType == Param::typeInt ? p.i : p.f );
 return 0;
}

My expected output would be for printf( "Number is %d\n", p.paramType == Param::typeInt ? p.i : p.f );

Number is -10

but it actually printed

Number is 0

I have put a breakpoint after initialization of the Param p, and although p.paramType is defined as typeInt, actual output for if was -10.0000. Checking p.f gives some undefined value as expected, and p.i shows -10 also as expected. But p.paramType == Param::typeInt ? p.i : p.f in watch window evaluates to -10.000000.

I added second printf that prints it as float and now the output is

Number is 0
Number is -10.000000

So actually why this happens? Is it possible that this is a bug in Visual Studio (I use VS2012)?

Update:

std::cout<< (p.paramType == Param::typeInt ? p.i : p.f);

gives correct value of -10.

timrau
  • 22,578
  • 4
  • 51
  • 64
Izzy
  • 402
  • 6
  • 16

2 Answers2

5

This is because the resulting type of the expression p.paramType == Param::typeInt ? p.i : p.f is always float (only the value is different depending on paramType), but your first format string expects an integer.

The following should work as you expect:

printf("Number is %d\n", (int)(p.paramType == Param::typeInt ? p.i : p.f));

The cout version gives you the expected output because the type of the expression being inserted (float) is deduced automatically, and so it formats it as a float. It is essentially the same as your second printf.

Cameron
  • 96,106
  • 25
  • 196
  • 225
  • Thank you for your reply. I cannot cast complete expression to int as p.paramType can be float also (i just used int as example above). Why it's always float, if I feed into integer part of union one number? – Izzy Oct 30 '15 at 22:29
  • 1
    If you don't want to cast it to int, then it doesn't make sense to print it out as an integer either. So either always print a float, or when you print an int actually give it an int :-) The result of an expression (such as `?:`) can only be of a single type; when the two subexpressions are of different types, [an implicit type conversion is done](http://stackoverflow.com/q/8535226/21475). – Cameron Oct 30 '15 at 22:32
  • Ah I see. I learned something new today :). I didn't know that about ternary if statement can returning only one type. But I can't cast all to int because it may be float instead of int. Solution is actually to leave all to float and make format as %.0f which will round float number to whole number. – Izzy Oct 30 '15 at 22:41
1

The problem is twofold. First, it's with printf(). Don't use printf() in C++ code. The reason is related to the second problem. printf() does not have type safety. It does not know what types you gave it.

The second reason is the way types work. Your ternary if statement always returns a float because that's the right-most type. Since an int can be implicitly converted to a float, nothing "bad" happens (unless you're to turn on various "extra" warnings). So p.i gets converted to a float but you're telling printf() to expect an int. Therefore, undefined behavior occurs. Your program doesn't crash but it doesn't give you what you expected.

inetknght
  • 4,300
  • 1
  • 26
  • 52
  • I like `printf`, even in C++. It's simple. Yes, there's a good argument from a safety perspective if you don't use it properly, but that argument can be extended to quite a lot of things in C++ :-) – Cameron Oct 30 '15 at 22:29
  • 2
    In my opinion, use of `std::ostream` (eg, through `std::cout`) is *also* simple. It's certainly far more extensible. – inetknght Oct 30 '15 at 22:35
  • 1
    Very true. I've never actually needed the extensibility, though, so far. – Cameron Oct 30 '15 at 22:36
  • It's also quite easy, when updating existing code (even code that's existed for just a few minutes) to modify the parameters or argument specification while forgetting to modify the other to reflect the changes. So you want to print a new value, you add it to the argument specification... but forgot to add it to the parameters? Nice crash there (usually...). If you added a parameter but forgot to add it to the argument specification, well you'll probably just spend some time wondering where is the data in your stdout. On the other hand, improper use of `cout` typically won't even compile. – inetknght Oct 30 '15 at 22:40
  • 1
    Actually I used printf as example because I have custom made function which mimics printf in some way. – Izzy Oct 30 '15 at 22:44
  • 1
    Actually I did try to set project settings to Warning Lvl4, and also turned on Code Analysis. VS didn't found any problem. – Izzy Oct 30 '15 at 22:45