【问题标题】:warning: narrowing conversion C++11警告:缩小转换 C++11
【发布时间】:2015-01-08 16:30:48
【问题描述】:

g++ 4.9.0 -O2 -std=c++11

template<class T>
struct vec3 {
    T x, y, z;
    vec3() = default;
    vec3(const vec3<T> &other) = default;
    vec3(T xx, T yy, T zz) { x = xx; y = yy; z = zz; }
    vec3<T> operator-(const vec3<T> &other) { 
      return vec3<T>{ x - other.x, y - other.y, z - other.z }; 
    }
};

int main() {
    vec3<char> pos{ 0, 0, 0 };
    vec3<char> newPos{ 0, 0, 0 };
    auto p = pos - newPos;

    return 0;
}

我收到警告:

!!warning: narrowing conversion of ‘(((int)((vec3<char>*)this)->vec3<char>::x) - ((int)other.vec3<char>::x))’ from ‘int’ to ‘char’ inside { } [-Wnarrowing]

但是,如果我在 operator- 函数中使用 (...) insted 或 {...} 执行此操作,则警告会消失。为什么?

【问题讨论】:

  • 看看这个GCC bug report的讨论,尤其是Jonathan Wakely's response
  • {} 捕获缩小转换(使代码格式错误)。 () 没有。或者你在问为什么它在缩小?
  • 是的,为什么变窄了?

标签: c++ templates c++11 narrowing


【解决方案1】:

首先,为什么要缩小?这来自§5/10:

许多期望算术或枚举类型的操作数的二元运算符会导致转换并以类似的方式产生结果类型。目的是产生一个通用类型,这也是结果的类型。这种模式称为常用算术转换,定义如下:

——[..]

— 否则,应在两个操作数上执行整数提升 (4.5)。

积分提升在 4.5/1 中定义:

boolchar16_tchar32_twchar_t 以外的整数类型的纯右值,其整数转换等级 (4.13) 小于 int 的等级可以转换为type int if int 可以表示源类型的所有值;否则,源纯右值可以转换为unsigned int 类型的纯右值。

在我们的例子中,我们有decltype(char + char)int,因为char 的转换排名低于int,所以在调用operator+ 之前两者都被提升为int。现在,我们将ints 传递给采用chars 的构造函数。根据定义(§8.5.4/7,特别是 7.4):

窄化转换是隐式转换

(7.4) — 从整数类型或无作用域枚举类型到不能表示原始类型的所有值的整数类型,除非源是常量表达式,其值在整数提升后将适合目标类型。

根据 §8.5.4/3 明确禁止列表初始化(强调我的,“见下文”实际上是指我刚刚在上面复制的内容):

T 类型的对象或引用的列表初始化定义如下

——[..]

——否则,如果T 是类类型,则考虑构造函数。枚举适用的构造函数,并通过重载决议(13.3、13.3.1.7)选择最佳构造函数。 如果需要缩小转换(见下文)来转换任何参数,则程序格式错误。 [...]

这就是为什么您的vec3&lt;T&gt;{int, int, int} 会向您发出警告:由于整数提升需要对所有表达式进行缩小转换,因此程序格式错误。现在,关于“格式错误”的陈述仅在列表初始化的上下文中出现。这就是为什么如果您在没有{}s 的情况下初始化向量,您不会看到该警告:

vec3<T> operator-(const vec3<T> &other) { 
    // totally OK: implicit conversion from int --> char is allowed here
    return vec3<T>( x - other.x, y - other.y, z - other.z );
}

至于解决这个问题——只调用没有列表初始化的构造函数可能是最简单的解决方案。或者,您可以继续使用列表初始化并仅模板化您的构造函数:

template <typename A, typename B, typename C>
vec3(A xx, B yy, C zz)
: x(xx) // note these all have to be ()s and not {}s for the same reason
, y(yy)
, z(yy)
{ } 

【讨论】:

    【解决方案2】:

    这里发生了一些事情。首先,{...} 语法禁止隐式缩小转换。因此,简单的解决方法是将大括号更改为括号:

    vec3<T> operator-(const vec3<T> &other) { 
      return vec3<T>( x - other.x, y - other.y, z - other.z ); 
    }
    

    第二件事是,“嗯?char减去一个char就是一个char,有什么问题?!”这里的答案是 C/C++ 想要使用 natural size 进行算术运算。这就是您在错误消息中看到(int) 的原因。 Here is a good explanation 为什么这样做(以防 StackOverflow 的答案消失,他引用了 C11 标准的 6.3.1.1)。

    因此,修复代码的另一种方法是:

    vec3<T> operator-(const vec3<T> &other) { 
      return vec3<T>{
        static_cast<char>(x - other.x),
        static_cast<char>(y - other.y),
        static_cast<char>(z - other.z)
        };
    }
    

    顺便说一句,Effective Modern C++ 中的第 7 项让我相信,有时() 更适合初始化,有时{} 更好。有时你只需要耸耸肩并使用另一个。

    【讨论】:

    • 简洁明了,准确解释了为什么我在尝试初始化 uint16_t 成员并添加 2 个 uint16_ts 时出错。 (例如Foo foo {x +y})。
    猜你喜欢
    • 2014-01-29
    • 2013-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-12
    • 1970-01-01
    • 2016-02-06
    相关资源
    最近更新 更多