<cmath> скрывает isnan в <math.h> в С++ 14/С++ 11?

У меня есть небольшое тестовое приложение, которое использует isnan из <math.h>:

#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;
}

Сборка и запуск:

Работает на С++ 98

$ g++ -std=c++98 main.cpp; ./a.out
1
1

С++ 11 и С++ 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)
     ^

Обратите внимание, что порядок включения не важен. Если я включаю <cmath> до <math.h> или после, результат будет таким же.

Вопросы

  • Почему isnan ушел?
  • Без необходимости возвращаться и изменять старый код для компиляции в соответствии с новым стандартом, есть ли способ исправить это?

Ответы

Ответ 1

Кратко подытоживая соответствующие пункты, в основном из Джонатана Вакели, отличный пост в блоге:

  • glibc < 2.23 math.h объявляет устаревший X/Open int isnan(double);, который несовместим с версией C99/С++ 11 (bool isnan(double);).
  • glibc 2.23 math.h исправляет это, не объявляя функцию isnan в С++ 11 или новее.
  • Все они все еще определяют макрос isnan. #include <cmath> уничтожает этот макрос в соответствии с требованиями стандарта С++.
  • GCC 6 libstdС++ предоставляет свой собственный специальный заголовок math.h, который объявляет bool isnan(double); в глобальном пространстве имен (если libc math.h не объявляет устаревшую подпись), а также уничтожает макросы в соответствии с требованиями стандарта.
  • До GCC 6, #include <math.h> просто включал заголовок из вашего libc, поэтому макрос не запускается.
  • #include <cmath> всегда уничтожает макросы.

Чистый результат в режиме С++ 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

Ответ 2

Если вы заглянете внутрь <cmath> из GCC, он имеет следующее:

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

Вот почему порядок не имеет значения - всякий раз, когда вы #include <cmath>, <math.h> включается автоматически, и его содержимое (частично) nuked.

Попытка включить его снова не будет иметь эффекта из-за #ifndef _MATH_H.


Теперь, что стандарт должен сказать об этом поведении?

[des.c.headers]:

... каждый заголовок С, каждый из которых имеет имя формы name.h, ведет себя так, как если бы каждое имя помещалось в стандартном пространстве имен библиотек соответствующим заголовком cname помещенных в область глобального пространства имен. Неизвестно, эти имена сначала объявляются или определяются в пространстве имен ([basic.scope.namespace]) пространства имен std и затем вводятся в глобальную область пространства имен с помощью явных использования-деклараций ([Namespace.udecl]).

[Пример: заголовок <cstdlib>, несомненно, предоставляет свои объявления и определения в пространстве имен std. Он может также предоставлять эти имена в глобальном пространстве имен. Заголовок <stdlib.h> несомненно предоставляет те же декларации и определения в глобальном пространство имен, как и в стандарте C. Он также может предоставлять эти имена в пространстве имен std. - конец примера]

Итак, хорошо, что <cmath> не предоставляет isnan в глобальном пространстве имен.

Но это серая область, что должно произойти, когда оба включены в одну единицу компиляции, хотя можно утверждать, что приведенное выше утверждение подразумевает, что обе версии должны взаимодействовать, и в этом случае это будет ошибка в GCC/libstdС++ (некоторые версии).

Ответ 3

Множество функций внутри math.h на самом деле являются макросами. Поскольку это не является идиоматическим С++, заголовок cmath содержит следующий код:

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

И реализует тогда все эти макросы undefined как функцию в namespace std. Это, по крайней мере, верно для gcc 6.1.1. Вот почему ваш компилятор не может найти isnan.