4

this is much like this question Why must a short be converted to an int before arithmetic operations in C and C++? however there is a sub-question why the compiler diagnoses a warning in one case and an error in the other case ,with the exact same expression.

I really like the use of the auto 'type' as in auto var =..., but MSVC 2015 CTP gives an error from my code.

The issue is I am auto-ing an expression of type short but sometimes it's promoted to int.

Here's a MCVE:

struct MY_COORD { short X; short Y; };
using t_crd = MY_COORD; 

void call_test ( t_crd x )  {}

int main()
{
    t_crd    crd { 10 ,20 };

    auto     x5 = crd.X - crd.Y;
    auto     y5 = crd.Y - crd.X;
    t_crd    crd5 { x5 ,y5 };       // (1)
    call_test( t_crd{ x5 ,y5 } );   // (2)
}

The messages from lines (1) and (2) are respectively:

 warning C4838: conversion from 'int' to 'short' requires a narrowing conversion
 error C2397: conversion from 'int' to 'short' requires a narrowing conversion

I thought this code was OK but it's not according to MSVC 2015 CTP compiler (but Intellisense doesn't indicate it). Is there some obscure little rule that I missed that makes MSVC right ?

And if so why is the same expression a warning in one case and an error in the other case ?

I wanted to use it in the initialization of a loop variable like this:

MY_COORD pos = ResultFromFunction();
auto rows = pos.Y;
for ( auto i = (rows - rows); i < rows; ++i )
{
   coord c{ 0 ,i };
   ...
}
Community
  • 1
  • 1
engf-010
  • 3,980
  • 1
  • 14
  • 25
  • 1
    If you really want a specific type, just specify the type. – Jerry Coffin Mar 16 '15 at 01:36
  • @Jerry Coffin : I want it to trace the type (if it changes) . If it doesn't work in this simple case ,then I can't use it in more complex cases.. – engf-010 Mar 16 '15 at 01:39
  • 2
    Sounds like you may want something more like `for (decltype(rows) i = 0; ...`? Obvious alternative would be to use a function template instead, so it just becomes `T` (or whatever name you prefer). – Jerry Coffin Mar 16 '15 at 01:46
  • @Jerry Coffin: yes ,but that take more typing (and thinking) ,I am very lazy :). But the real questions is ,is my code correct or is MSVC correct ? – engf-010 Mar 16 '15 at 01:50
  • Can you post an [MCVE](http://stackoverflow.com/help/mcve)? – R Sahu Mar 16 '15 at 01:56
  • @RSahu: the sample provided has all the info needed. (if not on windows you can comment out the include windows line an uncomment the two MY_COORD lines) if it compiles then my code is right and MSVC 2015 CTP is not. – engf-010 Mar 16 '15 at 02:01
  • The type of `x-x` is only sometimes the same as `x`. Use `decltype(x)`. – Yakk - Adam Nevraumont Mar 16 '15 at 02:16
  • @Yakk: Can you elaborate that in an answer. I like to know why ? – engf-010 Mar 16 '15 at 02:20
  • This is basically a duplicate of http://stackoverflow.com/questions/24371868/why-must-a-short-be-converted-to-an-int-before-arithmetic-operations-in-c-and-c – Bill Lynch Mar 16 '15 at 02:38
  • @Edwin for example , subtracting `char *` from `char *` gives `ptrdiff_t` – M.M Mar 16 '15 at 02:41
  • @Bill Lynch: thanks ,looking in to that. – engf-010 Mar 16 '15 at 02:41

2 Answers2

6

Arithmetic operators always promote integral types smaller than int to int before performing the operation. This is mandated in [expr]/10 of the C++11 standard:

Many binary operators that expect operands of arithmetic or enumeration type cause conversions and yield result types in a similar way. The purpose is to yield a common type, which is also the type of the result. This pattern is called the usual arithmetic conversions.

The sub-clause from there which applies to your case specifies that the operands in rows - rows will be subjected to integral promotion. In particular, according to [conv.prom]/1

A prvalue of an integer type other than bool, char16_t, char32_t, or wchar_t whose integer conversion rank is less than the rank of int can be converted to a prvalue of type int if int can represent all the values of the source type; otherwise, the source prvalue can be converted to a prvalue of type unsigned int.

Since the rank of short is guaranteed to be less than the rank of int, both the operands of rows - rows are promoted to int, giving the expression rows - rows a type of int.

About the narrowing conversion, the standard specifies(in [dcl.init.aggr]) that, in aggregate initialization, if the expression requires a narrowing conversion, the program is ill-formed. Compilers are free to diagnose it anyway they like. IIRC, under the simple circumstances like t_crd crd5 { x5 ,y5 };, clang 3.5 issues an error while g++ 4.9.2 issues a warning when used with the default warnings settings. Either way. MSVC's behaviour, even if slightly odd, is standard-conforming.

Pradhan
  • 16,391
  • 3
  • 44
  • 59
2
short x=1, y=2;
static_assert(
  std::is_same<int, decltype(x-y)>{},
  "subtracting two shorts gets an `int`, not a `short`"
);
// that is how integral promotion works in C++ -- things
// smaller than `int` promote to `int` almost always
int a=3,b=4;
// if you use the "new" {} style initialization
// it is an error to have a narrowing conversion:
//short z1 = {b-a};
// but not if you don't use {}:
short z2 = b-a;
(void)x;(void)y;(void)a;(void)b;(void)z2; // block unused var warnings.

live example

The variables you make auto are mostly all ints, as they are usually the difference between two shorts. You then proceed to convert them in various contexts to shorts -- sometimes in contexts where the compiler issues a warning, other times when it is an error to do a narrowing conversion.

When you replace auto with short the conversion warnings and errors go away.

Note that short-short can easily overflow a short. If it does, the result is undefined behavior (signed overflow is undefined). In practice, the hardware may do mod 2^n, but even on 2s complement hardware the optimizer is free to assume that no overflow occurs, and that can cause seemingly insane changes to your code during optimization.

A classic example is int x = (unsigned)-1; if (x<0) std::cout << "not printed\n"; std::cout << x << " == -1\n";, where the branch ofx<0can be eliminated as "impossible" by an optimizer (as we convertedx` from an unsigned value, it cannot be negative unless it overflows, and if it overflows that is undefined behavior, so anything goes).

So by changing auto to short you actually move the undefined behavior that the warnings and errors are talking about to a spot where the compiler doesn't warn you or generate errors. The problem still remains.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • thanks ,that's weird. But it doesn't explain why I get warnings/error when I use auto and not when I use short. – engf-010 Mar 16 '15 at 02:38
  • @Edwin How does it not? Your `auto` variables are of type `int`, as they are the same type as the rhs. Changing them to `short` makes them a different type, and the later `int` to `short` conversion warnings and errors of course go away. – Yakk - Adam Nevraumont Mar 16 '15 at 02:39
  • thanks for the further explanation ,makes more sense to me now. – engf-010 Mar 16 '15 at 02:55