<cmath> 在 C++14 / C++11 的 <math.h> 中隐藏 isnan?
Posted
技术标签:
【中文标题】<cmath> 在 C++14 / C++11 的 <math.h> 中隐藏 isnan?【英文标题】:<cmath> hides isnan in <math.h> in C++14 / C++11? 【发布时间】:2016-12-31 23:40:12 【问题描述】:我这里有一个小测试应用程序,它使用来自<math.h>
的isnan
:
#include <iostream>
#include <math.h>
int main()
double d = NAN;
std::cout << isnan(d) << '\n';
return 0;
在 3 种不同的标准下构建和运行:
$ g++ -std=c++98 main.cpp; ./a.out 1 $ g++ -std=c++11 main.cpp; ./a.out 1 $ g++ -std=c++14 main.cpp; ./a.out 1
现在我们还包括<cmath>
,并同时使用isnan
和std::isnan
进行测试:
#include <iostream>
#include <cmath>
#include <math.h>
int main()
double d = NAN;
std::cout << std::isnan(d) << '\n';
std::cout << isnan(d) << '\n';
return 0;
构建并运行:
C++98 作品
$ g++ -std=c++98 main.cpp; ./a.out 1 1
C++11 和 C++14 没有,找不到isnan
。
$ g++ -std=c++11 main.cpp main.cpp: In function ‘int main()’: main.cpp:10:25: error: ‘isnan’ was not declared in this scope std::cout << isnan(d) << '\n'; ^ main.cpp:10:25: note: suggested alternative: In file included from main.cpp:3:0: /usr/include/c++/5/cmath:641:5: note: ‘std::isnan’ isnan(_Tp __x) ^ $ g++ -std=c++14 main.cpp main.cpp: In function ‘int main()’: main.cpp:10:25: error: ‘isnan’ was not declared in this scope std::cout << isnan(d) << '\n'; ^ main.cpp:10:25: note: suggested alternative: In file included from main.cpp:3:0: /usr/include/c++/5/cmath:641:5: note: ‘std::isnan’ isnan(_Tp __x) ^
请注意,包含的顺序并不重要。如果我在<math.h>
之前或之后包含<cmath>
,结果是一样的。
问题
为什么isnan
不见了?
无需返回并更改旧代码以在新标准下编译,有什么办法可以解决这个问题吗?
【问题讨论】:
Clang 是使用 libc++,还是和 gcc 一样的 libstdc++? @ildjarnldd
说是一样的 (/usr/lib/x86_64-linux-gnu/libstdc++.so.6
)。我已编辑问题以删除 clang 部分。
您使用的是什么版本的编译器?我无法在 coliru 上使用 g++ 或 clang 进行复制。
看起来可能是库的错误。 gcc.godbolt.org 在 4.9.2 到 5.3 中失败,但在 6.1 上有效
@SteveLorimer 如果您在几天内没有找到任何东西,请给我一个 ping,我会悬赏。我真的很想知道发生了什么。
【参考方案1】:
简单总结一下相关点,大多来自Jonathan Wakely's excellent blog post:
glibc math.h 声明了与 C99/C++11 版本 (bool isnan(double);
) 不兼容的过时 X/Open int isnan(double);
。
glibc 2.23 的 math.h
通过不在 C++11 或更高版本中声明 isnan
函数来解决此问题。
它们仍然定义了一个isnan
宏。 #include <cmath>
按照 C++ 标准的要求核对该宏。
GCC 6 的 libstdc++ 提供了自己特殊的 math.h
标头,该标头在全局命名空间中声明了一个 bool isnan(double);
(除非 libc math.h
声明了过时的签名),并且还按照标准的要求对宏进行了核对。
在 GCC 6 之前,#include <math.h>
仅包含来自您的 libc 的标头,因此宏不会被核化。
#include <cmath>
总是核对宏。
净结果,在 C++11 模式下:
glibc < 2.23, GCC < 6: <math.h> uses the macro; <cmath> uses obsolete signature
glibc >= 2.23, GCC < 6: <math.h> uses the macro; <cmath> results in error
glibc < 2.23, GCC >= 6: <math.h> and <cmath> use obsolete signature
glibc >= 2.23, GCC >= 6: <math.h> and <cmath> use standard signature
【讨论】:
@NathanOliver 你看到这个答案了吗? @SteveLorimer 我现在有。谢谢 T.C.另一个很好的答案。【参考方案2】:如果你从 GCC 内部查看 <cmath>
,它有:
. . .
#include <math.h>
. . .
#undef isnan
这就是顺序无关紧要的原因 - 每当您 #include <cmath>
、<math.h>
被自动包含并且其内容(部分)被删除时。
由于#ifndef _MATH_H
,尝试再次包含它将无效。
现在,标准对这种行为有什么看法?
[depr.c.headers]:
... 每个 C 标头,每个 其中有一个
name.h
形式的名称,表现得好像每个名称都放置了 在标准库命名空间中,对应的 cname 标头是 放置在全局命名空间范围内。未指定是否 这些名称首先在命名空间范围内声明或定义 ([basic.scope.namespace]) 命名空间std
然后被注入 通过显式 using-declarations 进入全局命名空间范围 ([namespace.udecl])。[ 示例:标题
<cstdlib>
确实提供了它的声明 和命名空间std
中的定义。它可能还提供这些 全局命名空间中的名称。标头<stdlib.h>
肯定 在全局范围内提供相同的声明和定义 命名空间,就像在 C 标准中一样。它还可能提供这些名称 在命名空间std
内。 —结束示例]
所以<cmath>
在全局命名空间中不提供isnan
是可以的。
但是当两个都包含在一个编译单元中时会发生什么是一个灰色地带,尽管有人可能会争辩说上面的陈述暗示两个版本必须互操作,在在这种情况下,这将是 GCC/libstdc++(某些版本)中的错误。
【讨论】:
【参考方案3】:math.h
中的很多函数实际上都是宏。由于这不是惯用的 C++ 标头 cmath
包含以下代码:
...
#undef isinf
#undef isnan
#undef isnormal
...
然后将所有未定义的宏实现为namespace std
中的函数。至少对于 gcc 6.1.1 是这样。这就是为什么你的编译器找不到isnan
。
【讨论】:
我可以同时编译std::isnan
和isnan
on gcc-6.1
on godbolt
如果是undef的问题,包含的顺序很重要,但没关系
@AntonMalyshev :为什么顺序很重要? cmath
几乎总是会包含 math.h
,它将有一个标头保护——它总是会被包含一次,而且只包含一次。以上是关于<cmath> 在 C++14 / C++11 的 <math.h> 中隐藏 isnan?的主要内容,如果未能解决你的问题,请参考以下文章