【问题标题】:g++4.9 and g++5 different behaviour when narrowing in initializing listg++4.9 和 g++5 在初始化列表中缩小时的不同行为
【发布时间】:2015-02-11 22:48:47
【问题描述】:

考虑这段代码:

#include <iostream>

int main()
{
    int i{10.1}; // narrowing, should not compile
    std::cout << i << std::endl;
}

根据 C++11 标准,它不应该编译(大括号初始化中的变窄是禁止的。)

现在,使用g++4.9.2 -std=c++11 编译只会发出警告

warning: narrowing conversion of '1.01e+1' from 'double' to 'int' inside { } [-Wnarrowing]

删除-std=c++11 标志会导致有关大括号初始化的警告,但不会缩小:

warning: extended initializer lists only available with -std=c++11 or -std=gnu++11

另一方面,g++5 不会编译它,前提是您使用g++5 -std=c++11 编译。但是,如果省略了-std=c++11,那么即使g++5 也会愉快地编译它,只给出与大括号初始化相关的警告,而不是缩小:

warning: extended initializer lists only available with -std=c++11 or -std=gnu++11

上述行为似乎有问题,g++4.9 不应该编译代码,如果你忘记指定 -std=c++11g++5 编译它就更奇怪了。这是一个已知问题吗?

【问题讨论】:

  • @MattMcNabb 即使是这种情况,在没有 -std=c++11 的情况下编译时,g++5 至少应该发出警告而且让我感到困惑的事实是,即使在 Stroustroup页面,stroustrup.com/C++11FAQ.html#narrowing 他谈到“如何防止缩小”。 IMO,只是发出警告并不能阻止任何事情。
  • 这与 int foo = 1.0f 相同,它可以在任何地方编译,但如果级别足够高(msvc 上的 lvl 4)会生成警告
  • @paulm 它并不完全一样。大括号初始化的“特性”之一就是这个特性,它可以防止变窄。但看起来编译器只需要发出警告。在上面的链接中,B. Stroustroup 甚至说:“但是,在 C++11 中,{} 初始化不会缩小:...”

标签: c++ c++11 gcc4.9 gcc5


【解决方案1】:

{} 内部的窄化转换只是 C++11 模式下的错误,原因很简单:它不是 C++03 中的错误。现在,T var{value}; 是新的 C++11 语法,但 T var = {value}; 已经是有效的 C++03 语法,并且确实允许缩小转换范围。

int i = { 10.1 }; // valid C++03, invalid C++11

它使 GCC 开发人员更容易在 T var{value};T var={value}; 初始化中处理缩小转换。这很有用,因为它避免了编译器中警告的两个单独的代码路径。

它使 GCC 开发人员更容易接受 C++03 模式下的 T var{value}; 语法,只是警告它。在 C++03 模式下还启用了其他几个 C++11 语法扩展。这很有用,因为在 GCC 的标准库实现中使用了几个 C++11 语法扩展(关于它的警告被禁止)。

之所以int i{10.1};不是在 C++11 模式下在 GCC 4.9 中出错,而在 GCC 5 中却出错,是因为没有将其视为错误导致有效被拒绝的代码。 C++ 标准要求将其视为 SFINAE 上下文中的错误,这里是一个有效的 C++11 程序,因此在 GCC 4.9 中运行不正确:

#include <stdio.h>
template <typename T> void f(double) { puts("ok"); }
template <typename T, typename = decltype(T{10.1})> void f(int) { puts("error"); }
int main() { f<int>(1); }

这应该打印“ok”。第二个重载应该被丢弃。

使用 GCC 4.9,它会打印“错误”,因为不会丢弃第二个重载,并且 intdouble 更匹配。

【讨论】:

  • 只是为了确保我理解,f&lt;int&gt;() 的调用在 g++49 中会变得模棱两可,对吧,由于允许转换?而在 g++5 中,第二个由于 SFINAE 而被拒绝。您可能是指第一行中的f()
  • @vsoftco 是的,GCC 4.9 报告它不明确。我的意思是...,希望诱使它更喜欢第二个声明(这意味着它会生成错误代码而不是仅仅拒绝有效代码),但忘记了... 对此不起作用。我会用一个更好的例子来编辑它,当我有一个时,GCC 4.9 确实会生成错误的代码。 :)
  • 我想我有一个例子,你介意我将它添加到你的答案中吗?
  • @vsoftco 感谢您的提议。在您发表评论后,我刚刚在几秒钟内编辑了一个。你的和我的一样吗?
  • #include &lt;iostream&gt; template &lt;typename T, typename... U&gt; void f(U...) { std::cout &lt;&lt; "first" &lt;&lt; std::endl; } template &lt; typename T, typename = decltype(T {10.1}) &gt; void f() { std::cout &lt;&lt; "second" &lt;&lt; std::endl; }; int main() { f&lt;int&gt;(); } 所以味道差不多。您关于生成错误代码的评论非常好,在这种情况下问题变得很严重。
【解决方案2】:

标准从不说“不应该编译”#error 除外)。某些格式错误的程序必须发出诊断并发出警告以满足这一要求。

您可以使用开关-Werror 使 gcc 停止所有诊断的编译。它可以缩小到特定的警告,例如-Werror=narrowing.

如果您使用 GNU++ 或任何默认模式而不是 C++11 进行编译,那么编译器可以做任何它喜欢的事情,包括毫无怨言地接受缩小转换。

参考:N3936 [intro.compliance]/2

  • 如果程序包含违反任何可诊断规则 [...],则符合要求的实现应发出至少一条诊断消息。

  • 如果一个程序违反了不需要诊断的规则,则本国际标准对该程序的实现没有任何要求。

[defns.diagnostic]

诊断信息

属于实现输出消息的实现定义子集的消息

还要注意第一个要点,消息的数量或内容不需要与违规的数量或内容相对应。

标准完全由编译器决定如何组织其错误和/或警告,附带条件是对于某些违规行为它不能默默地忽略它。

【讨论】:

  • 谢谢。让我感到困惑的事实是,如果您查看标准,您会看到标有// error 注释的缩小声明,与例如相同。 int&amp; r; // error。现在,常识告诉我,当您使用error 评论某些内容时,它不应该是可编译的。我不得不承认,我不知道“格式错误的程序”只需要发出诊断信息而不是编译时错误。
  • 通过说明它在标准中的位置,这个答案会更完整。
  • @vsoftco 由 gcc/g++ 的开发人员决定将特定条件报告为“错误”还是“警告”的默认设置。我想他们会尽量满足他们的用户群抱怨最少的事情
  • Pradhan 还提出了一个很好的观点,即编译器通常会在具有适当扩展名的情况下使用“警告”
  • C++ 标准甚至没有说 #error 不应该编译,不像 C。见 open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#745
【解决方案3】:

引用1.4 [intro.compliance]

符合标准的实现可能有扩展(包括额外的 库函数),只要它们不改变任何 格式良好的程序。诊断程序需要实现 使用这种格式错误的扩展名 国际标准。然而,这样做之后,他们可以编译和 执行此类程序。

您的初始化示例的适用部分是8.5.4 [dcl.init.list]。特别是,

否则,如果初始化列表只有一个 E 类型的元素并且 T 不是引用类型或其引用类型是 与 E 相关的引用,对象或引用从 该元素;如果需要缩小转换(见下文) 将元素转换为 T ,程序是格式错误的

附上例子

int x1 {2}; // OK
int x2 {2.0}; // error: narrowing

由于诊断的确切性质是指定的实现,因此观察到的两组行为都符合标准。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-22
    • 1970-01-01
    • 2019-07-25
    • 2022-01-04
    • 2015-01-19
    • 2014-12-26
    • 2012-11-09
    相关资源
    最近更新 更多