<cmath> 在 C++14 / C++11 中隐藏 Isnan<math.h>?

<cmath> hides isnan in <math.h> in C++14 / C++11?

本文关键字:gt lt Isnan math 隐藏 cmath C++14 C++11      更新时间:2023-10-16

我这里有一个小的测试应用程序,它使用来自<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>,并使用isnanstd::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走了?
  • 无需返回并更改旧代码以在新标准下编译,有没有办法解决此问题?

简要总结相关观点,主要来自Jonathan Wakely的优秀博客文章:

  • glibc <2.23 的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

如果你从GCC查看内部<cmath>,它有这个:

. . .
#include <math.h>
. . .
#undef isnan

这就是为什么顺序无关紧要的原因 - 每当您#include <cmath>时,<math.h>都会自动包含,并且其内容(部分(被核弹。

由于 #ifndef _MATH_H ,尝试再次包含它不会有任何效果。


现在,标准对这种行为有什么看法?

[depr.c.headers]:

。每个 C 标头,每个 其中有一个形式name.h的名称,其行为就像每个名称放置一样 在标准库命名空间中,相应的 C名称标头为 放置在全局命名空间范围内。目前尚不清楚是否 这些名称首先在命名空间范围内声明或定义 ([basic.scope.namespace]( 的命名空间std,然后注入 通过显式 using 声明进入全局命名空间范围 ([命名空间.udecl](。

[ 示例:标头<cstdlib>确保提供其声明 和命名空间中的定义 std .它还可以提供这些 全局命名空间中的名称。标题肯定<stdlib.h> 在全局 命名空间,就像在 C 标准中一样。它还可能提供这些名称 在命名空间std . — 结束示例 ]

因此,<cmath> 不在全局命名空间中提供isnan是可以的。

但是当两者都包含在一个编译单元中时会发生什么是一个灰色地带,尽管有人可能会争辩说上面的陈述意味着两个版本都必须互操作,在这种情况下,这将是GCC/libstdc++(某些版本(中的一个错误。

math.h中的许多函数实际上是宏。由于这不是惯用的 c++,因此标头cmath包含以下代码:

    ...
    #undef isinf
    #undef isnan
    #undef isnormal
    ...

然后将所有这些未定义的宏实现为namespace std中的函数。至少对于 gcc 6.1.1 来说是正确的。这就是为什么您的编译器找不到isnan的原因。