<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
.