C++0x中的窄化转换。这是我的问题,还是听起来像是一个破坏性的改变?

93

C++0x将会禁止下面这段代码以及类似的代码,因为它需要将一个双精度浮点数double缩小转换(也称为narrowing conversion)成一个整型int

int a[] = { 1.0 };

我想知道这种初始化在实际代码中是否经常使用。如果进行更改,会有多少代码会出现问题?如果受到影响,修复这个问题需要多大的努力?


请参阅n3225中的8.5.4/6节。

缩小转换是一种隐式转换

  • 从浮点类型到整数类型,或
  • 从 long double 到 double 或 float,或者从 double 到 float,但源是常量表达式且转换后的实际值在可以表示的值范围内(即使不能完全表示),或
  • 从整数类型或未作用域枚举类型到浮点类型,但源是常量表达式且转换后的实际值将适合于目标类型并且在转换回原始类型时将产生原始值,或
  • 从整数类型或未作用域枚举类型转换为无法表示所有原始类型值的整数类型,但源是常量表达式且转换后的实际值将适合于目标类型并且在转换回原始类型时将产生原始值。

1
假设这仅适用于内置类型的初始化,我看不出这会有什么问题。当然,这可能会破坏一些代码。但应该很容易修复。 - Johan Kotlinski
1
@John Dibling:不,当值可以被目标类型精确表示时,初始化不是不良形式。(而且0已经是一个int了。) - aschepler
2
@Nim:请注意,这仅在{花括号初始化器}内部是不规范的,而这些的唯一遗留用法是用于数组和POD结构。此外,如果现有代码在适当的位置具有显式转换,则不会出现错误。 - aschepler
4
根据该工作文件,int a = 1.0; 仍然有效。 - Johannes Schaub - litb
1
@litb:谢谢。实际上我觉得这很容易理解,但令人失望——在我看来,从C++一开始就要求所有缩小转换都需要显式语法会更好。 - j_random_hacker
显示剩余7条评论
8个回答

42

我在使用GCC时遇到了这个重大变化。编译器会针对类似以下代码打印错误:

void foo(const unsigned long long &i)
{
    unsigned int a[2] = {i & 0xFFFFFFFF, i >> 32};
}

在函数void foo(const long long unsigned int&)中:

错误: 在{ }内部将(((long long unsigned int)i) & 4294967295ull)long long unsigned int转换为unsigned int时发生缩小转换。

错误: 在{ }内部将(((long long unsigned int)i) >> 32)long long unsigned int转换为unsigned int时发生缩小转换。

幸运的是,这些错误信息很直观,修复起来也很简单:

void foo(const unsigned long long &i)
{
    unsigned int a[2] = {static_cast<unsigned int>(i & 0xFFFFFFFF),
            static_cast<unsigned int>(i >> 32)};
}

这段代码在外部库中,只出现了两次,并且只在一个文件中。我认为这个破坏性变更不会影响太多的代码。但是新手可能会感到困惑,因为这里有转换问题


10

如果我发现过去12年写的任何C++代码都有这种问题,我会感到惊讶和失望。但是,除非我漏了什么,否则大多数编译器一直会提示任何编译时的“narrowings”警告。

这些也是窄化转换吗?

unsigned short b[] = { -1, INT_MAX };

如果是这样,我认为它们可能比你的浮点型转整型示例更常见。


1
我不明白为什么你说这在代码中是一个常见的事情。使用-1或INT_MAX而不是USHRT_MAX的逻辑是什么?USHRT_MAX在2010年末的climits中不存在吗? - user2918461

10
尝试将-Wno-narrowing添加到CFLAGS中,例如:
CFLAGS += -std=c++0x -Wno-narrowing

在C++编译器中,应该使用CPPFLAGS(当然这取决于您的构建系统或Makefile)。 - Mikolasan

9

我遇到的一个实际例子:

float x = 4.2; // an input argument
float a[2] = {x-0.5, x+0.5};

数字字面值隐式地被认为是 double 类型,这会导致提升。

2
所以通过写入“0.5f”将其转换为浮点数。 ;) - underscore_d
3
如果 float 是一个类型定义或模板参数则不能工作(至少不能损失精度),但重点是原来的代码在正确的语义下能够工作,并且在 C++11 中成为了一个错误。也就是说,这是一种“破坏性变更”的定义。 - Jed

7
我不会感到惊讶如果有人被类似以下问题所困扰:
float ra[] = {0, CHAR_MAX, SHORT_MAX, INT_MAX, LONG_MAX};

在我的实现中,当转换回int/long时,最后两个不会产生相同的结果,因此是缩小的。

我不记得自己曾经写过这个,除非近似于限制对某些事情有用。

这也至少似乎是有道理的:

void some_function(int val1, int val2) {
    float asfloat[] = {val1, val2};    // not in C++0x
    double asdouble[] = {val1, val2};  // not in C++0x
    int asint[] = {val1, val2};        // OK
    // now do something with the arrays
}

但这并不完全令人信服,因为如果我知道我只有两个值,为什么要将它们放在数组中,而不是直接使用float floatval1 = val1, floatval1 = val2;?然而,这样编译(并且在程序的可接受精度范围内工作)的动机是什么?为什么float asfloat[] = {val1, val2};不能呢?无论哪种方式,我都是从两个整数初始化两个浮点数,只是在一种情况下,这两个浮点数恰好是聚合体的成员。

在某些情况下,非常量表达式导致缩小转换,即使(在特定实现上),源类型的所有值都可以表示为目标类型并且可以转换回其原始值,这似乎特别严格:

char i = something();
static_assert(CHAR_BIT == 8);
double ra[] = {i}; // how is this worse than using a constant value?

假设没有错误,修复问题的方法应该总是将转换变得显式。除非您在使用宏时有某些奇怪的操作,否则我认为数组初始化器只会出现在数组类型附近,或者至少是类似于表示类型的东西,这可能取决于模板参数。因此,如果详细说明,强制类型转换应该很容易。


10
为什么要把只有两个值的信息放在数组中呢?比如像OpenGL这样的API就需要这样做。 - Georg Fritzsche

4

窄化转换错误与隐式整数提升规则相互作用不佳。

我遇到了一个看起来像下面这样的代码错误

struct char_t {
    char a;
}

void function(char c, char d) {
    char_t a = { c+d };
}

这会导致缩小转换错误(根据标准是正确的)。原因是c和d隐式地提升为int,而生成的int不能在初始化列表中缩小回char。

另一方面

void function(char c, char d) {
    char a = c+d;
}

当然,这仍然是可以的(否则一切都将变得一团糟)。但令人惊讶的是,即使

template<char c, char d>
void function() {
    char_t a = { c+d };
}

如果c和d的和小于CHAR_MAX,那么这段代码是可以正常编译且没有警告的。我仍然认为这是C++11的一个缺陷,但是那里的人们持不同意见 - 可能是因为没有容易修复的方法,要么就是放弃隐式整数转换(这是过去的遗留物,当人们编写像char a=b*c/d这样的代码时,即使(b * c)> CHAR_MAX,他们也希望它能正常工作),或者收窄转换错误(这可能是一件好事)。


我遇到了以下非常烦人的无聊问题:unsigned char x; static unsigned char const m = 0x7f; ... unsigned char r = { x & m }; <-- 大括号内部存在缩小转换。真的吗?所以运算符&也会将unsigned char隐式转换为int?好吧,我不在乎,结果仍然保证是一个unsigned char,唉。 - Carlo Wood
隐式整数转换提升? - curiousguy

4
这确实是一个重大变化,因为实际经验表明,由于将C++03代码库移植到C++11时遇到的实际问题,gcc已将缩小转换从错误更改为警告。请参见gcc错误报告中的此评论

标准只要求“符合规范的实现应发出至少一条诊断消息”,因此编译程序时允许发出警告。正如Andrew所说,-Werror=narrowing可以使其成为错误。

G++ 4.6曾给出错误,但故意在4.7中更改为警告,因为许多人(包括我自己)发现缩小转换是尝试将大型C++03代码库编译为C++11时最常遇到的问题之一。以前良好的代码,例如char c [] = {i,0};(其中i将始终在char范围内),会导致错误,并且必须更改为char c [] = {(char)i,0}


参见:https://gcc.gnu.org/wiki/FAQ#Wnarrowing - Amir Kirsh

1

看起来GCC-4.7不再因为缩小转换而产生错误,而是产生警告。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接