自动类型推导没有按预期工作

auto type deduction not working as expected

这很像这个问题Why must a short be converted to an int before arithmetic operations in C and C++? 但是有一个子问题,为什么编译器在一种情况下诊断为警告而在另一种情况下诊断为错误,表达式完全相同。

我真的很喜欢 auto 'type' 在 auto var =... 中的使用,但是 MSVC 2015 CTP 从我的代码中给出了一个错误。

问题是我正在 auto-ing 类型 short 的表达式,但有时它被提升为 int.

这是一个 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)
}

第(1)行和第(2)行的消息分别是:

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

我认为这段代码没问题,但它不是根据 MSVC 2015 CTP 编译器(但 Intellisense 没有指出)。 是否有一些我错过的模糊小规则使 MSVC 正确?

如果是这样,为什么同一个表达式在一种情况下是 warning 而在另一种情况下是 error

我想像这样在循环变量的初始化中使用它:

MY_COORD pos = ResultFromFunction();
auto rows = pos.Y;
for ( auto i = (rows - rows); i < rows; ++i )
{
   coord c{ 0 ,i };
   ...
}
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

您创建的变量 auto 大部分都是 int,因为它们通常是两个 short 之间的差值。然后,您继续在各种上下文中将它们转换为 shorts —— 有时在编译器发出警告的上下文中,有时在进行缩小转换时出错。

当您将 auto 替换为 short 时,转换警告和错误就会消失。

注意 short-short 很容易溢出 short。如果是,则结果是未定义的行为(有符号溢出是未定义的)。实际上,硬件可能会执行 mod 2^n,但即使在 2s 补码硬件上,优化器也可以自由假设没有发生溢出,这可能会导致在优化期间对代码进行看似疯狂的更改。

一个经典的例子是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`从一个无符号值开始,它不能为负,除非它溢出,如果它溢出是未定义的行为,那么任何事情都会发生。

因此,通过将 auto 更改为 short,您实际上将警告和错误所提及的未定义行为移动到编译器不会警告您或生成错误的位置。问题依旧。

算术运算符总是在执行运算之前将小于 int 的整数类型提升为 int。这是 C++11 标准的 [expr]/10 中强制要求的:

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.

那里适用于您的情况的子条款指定 rows - rows 中的操作数将进行积分提升。特别是,根据 [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.

由于 short 的秩保证小于 int 的秩,因此 rows - rows 的两个操作数都提升为 int,给出表达式rows - rows 一种 int.

关于收缩转换,标准规定(在[dcl.init.aggr]中),在聚合初始化中,如果表达式需要收缩转换,程序是病式的。编译器可以随意诊断它。 IIRC,在 t_crd crd5 { x5 ,y5 }; 这样的简单情况下,clang 3.5 发出错误,而 g++ 4.9.2 在使用默认警告设置时发出警告。无论哪种方式。 MSVC 的行为,即使有点奇怪,也是符合标准的。