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

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

我这里有一个小型测试应用程序,它使用来自 <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>,结果是一样的。

问题

math.h里面的很多函数其实都是宏。由于这不是惯用的 c++,因此 header cmath 包含以下代码:

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

然后将所有那些未定义的宏实现为 namespace std 中的函数。这至少适用于 gcc 6.1.1。这就是为什么你的编译器找不到 isnan.

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

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

这就是顺序无关紧要的原因 - 每当您 #include <cmath><math.h> 被自动包含并且其内容被(部分)删除时。

由于 #ifndef _MATH_H.

,尝试再次包含它不会有任何效果

现在,标准对这种行为有何规定?

[depr.c.headers]:

... every C header, each of which has a name of the form name.h, behaves as if each name placed in the standard library namespace by the corresponding cname header is placed within the global namespace scope. It is unspecified whether these names are first declared or defined within namespace scope ([basic.scope.namespace]) of the namespace std and are then injected into the global namespace scope by explicit using-declarations ([namespace.udecl]).

[ Example: The header <cstdlib> assuredly provides its declarations and definitions within the namespace std. It may also provide these names within the global namespace. The header <stdlib.h> assuredly provides the same declarations and definitions within the global namespace, much as in the C Standard. It may also provide these names within the namespace std. — end example ]

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

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

简单总结一下中肯的观点,大部分来自Jonathan Wakely's excellent blog post:

  • 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 header,它在全局命名空间中声明了一个 bool isnan(double);(除非 libc math.h 声明了过时的签名)并且还根据标准要求核对宏。
  • 在 GCC 6 之前,#include <math.h> 只是包含来自您的 libc 的 header,因此宏没有被破坏。
  • #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