<cmath>隐藏了C++ 14/C++ 11中<math.h>中的isnan?

Ste*_*mer 21 c++ nan cmath c++11 c++14

我这里有它使用一个小的测试应用程序isnan的<math.h>:

#include <iostream>
#include <math.h>

int main()
{
    double d = NAN;

    std::cout << isnan(d) << '\n';

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

根据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
Run Code Online (Sandbox Code Playgroud)

现在我们还包括<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;
}
Run Code Online (Sandbox Code Playgroud)

构建并运行:

C++ 98可以工作

$ g++ -std=c++98 main.cpp; ./a.out
1
1
Run Code Online (Sandbox Code Playgroud)

isnan找不到C++ 11和C++ 14 .

$ 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)
     ^
Run Code Online (Sandbox Code Playgroud)

请注意,包含的顺序并不重要.如果我<cmath>在之前<math.h>或之后包括,结果是相同的.

问题

  • 为什么isnan不见了?
  • 无需返回并更改旧代码以在新标准下编译,有没有办法解决这个问题?

T.C*_*.C. 19

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

  • 的glibc <2.23的math.h声明过时的X/Open int isnan(double);,其与C99/C++ 11版本不兼容(bool isnan(double);).
  • glibc 2.23 math.h通过不在isnanC++ 11或更高版本中声明函数来解决这个问题.
  • 他们都还定义了一个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
Run Code Online (Sandbox Code Playgroud)


rus*_*tyx 8

如果<cmath>从GCC 看内部,它有:

. . .
#include <math.h>
. . .
#undef isnan
Run Code Online (Sandbox Code Playgroud)

这就是为什么顺序并不重要-只要你#include <cmath>,<math.h>是自包含和被(部分)核爆其内容.

试图再次包含它将无效,因为#ifndef _MATH_H.


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

[depr.c.headers]:

...每个C头,每个都有一个表单的名称name.h,就好像每个由相应的c 名称头放在标准库名称空间中的名称放在全局名称空间范围内.未指定是否首先在命名空间的命名空间范围([basic.scope.namespace])中声明或定义这些名称std,然后通过显式using-declarations ([namespace.udecl])将这些名称注入全局命名空间范围.

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

所以<cmath>没有isnan在全局命名空间中提供它是可以的.

但是当它们都被包含在一个编译单元中时,它应该是一个灰色区域,尽管可以说上面的语句暗示两个版本必须互操作,在这种情况下它将是GCC/libstdc ++(某些版本)中的错误.