【问题标题】:Why is an explicit construction considered an (implicit) narrowing conversion?为什么将显式构造视为(隐式)缩小转换?
【发布时间】:2020-01-21 02:39:33
【问题描述】:

考虑以下代码:

uint32_t foo(uint64_t x ) {
    auto y = uint32_t { x };
    return y;
}

编译器觉得有必要警告我(GCC 9)甚至声明错误(clang 9),这被认为是一种缩小转换:GodBolt

我的问题:

  1. 为什么uint32_t { x } 不如static_cast<uint32_t>(x) 明确?
  2. 为什么使用 clang 比使用 GCC 更严重,导致错误?

【问题讨论】:

    标签: c++ g++ language-lawyer clang++ narrowing


    【解决方案1】:

    为什么uint32_t { x } 不如static_cast<uint32_t>(x) 明确?

    这不是不太明确,只是不允许。进行直接或复制列表初始化时不允许缩小转换。当您执行auto y = uint32_t { x }; 时,您正在通过缩小转换直接列表初始化y。 (保证复制省略意味着这里不再有临时)

    为什么使用 clang 比使用 GCC 更严重,值得出错?

    这取决于实施者。显然,clang 想要更严格并发出硬错误,但两者都很好。该标准只要求给出诊断消息,并给出警告或错误。

    【讨论】:

      【解决方案2】:

      添加到@NathanOliver 的答案 - 如果我们像这样构造 32 位整数,警告和错误就会消失:

      uint32_t foo(uint64_t x ) {
          auto y = uint32_t(x);
          return y;
      }
      

      所以,这里的(x){x} 在语义上不等效(即使同一个构造函数最终会被调用,如果它是一个类的话)。标准中的不缩小保证显然仅适用于列表初始化,IIANM。

      因此,如果您想格外小心(或者如果您不想被打扰,请使用括号。)

      【讨论】:

        【解决方案3】:

        来自https://en.cppreference.com/w/cpp/language/list_initialization

        缩小转化范围

        list-initialization 通过以下方式限制允许的隐式转换 禁止:

        ...

        - 从整数或无范围枚举类型转换为不能表示原始所有值的整数类型,除非 source 是一个常量表达式,其值可以精确地存储在 目标类型

        这听起来像是 clang 比 gcc 更符合这里(尽管请注意我不是语言律师)*:标准规定,如果您使用初始化列表,则不会有任何缩小转换的危险。这是一种有意识的设计选择,可以弥补语言中内置的相当混杂的隐式转换 - 而且您在示例中清楚地拼写它的方式无疑是一个附带的烦恼。

        编辑:* 并没有花很长时间 - 根据 NathanOliver 的回答,cppreference 中的“不允许”似乎转换为标准中的“实施者依赖”。这就是我不检查来源的结果。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-10-29
          • 1970-01-01
          • 2010-12-01
          • 1970-01-01
          • 2013-10-05
          • 2016-02-20
          • 2011-12-10
          • 2023-03-30
          相关资源
          最近更新 更多