Почему isnan неоднозначен и как этого избежать?
Поскольку isnan
может быть макросом (в C++98) или функцией, определенной в пространстве имен std
(в C++11), этот простой пример иллюстрирует очевидный (и, возможно, наивный) способ написания кода, который работает в обоих случаях.
#include <cmath>
int main() {
double x = 0;
using namespace std;
isnan(x);
}
Однако его компиляция выдает ошибки как в GCC (с -std = C++11), так и в Clang:
test.cc: In function ‘int main()’:
test.cc:6:10: error: call of overloaded ‘isnan(double&)’ is ambiguous
isnan(x);
^
test.cc:6:10: note: candidates are:
In file included from /usr/include/features.h:374:0,
from /usr/include/x86_64-linux-gnu/c++/4.8/bits/os_defines.h:39,
from /usr/include/x86_64-linux-gnu/c++/4.8/bits/c++config.h:426,
from /usr/include/c++/4.8/cmath:41,
from test.cc:1:
/usr/include/x86_64-linux-gnu/bits/mathcalls.h:234:1: note: int isnan(double)
__MATHDECL_1 (int,isnan,, (_Mdouble_ __value)) __attribute__ ((__const__));
^
In file included from test.cc:1:0:
/usr/include/c++/4.8/cmath:626:3: note: constexpr bool std::isnan(long double)
isnan(long double __x)
^
/usr/include/c++/4.8/cmath:622:3: note: constexpr bool std::isnan(double)
isnan(double __x)
^
/usr/include/c++/4.8/cmath:618:3: note: constexpr bool std::isnan(float)
isnan(float __x)
^
Почему это неоднозначно в C++ 11 и как заставить его работать как с C++ 98, так и с C++ 11 предпочтительно без слишком большой условной компиляции?
4 ответа
Это libstdc++
Ошибка, задокументированная в отчете об ошибке, std-функции конфликтуют с функциями C при сборке с поддержкой C++0x (и с использованием пространства имен std) с воспроизводящим образцом, очень похожим на OP:
#include <stdlib.h>
#include <cmath>
#include <stdio.h>
using namespace std;
int main(int argc, char** argv)
{
double number = 0;
if (isnan(number))
{
printf("Nan\n");
}
return 0;
}
и один из комментариев говорит:
Я не думаю, что это проблема, потому что libstdC++ всегда объявлял имена в глобальном пространстве имен, даже если это не было допустимо в C++ 03 - мы не изменили это для C++0x (все, что произошло, это стандарт был смягчен, чтобы отразить реальность реальных реализаций)
Это может в конечном итоге быть исправлено до тех пор, пока решение, представленное в отчете об ошибке, будет следующим:
Определите isnan явно, вызвав либо::isnan, либо std::isnan
С помощью ::isnan
насколько я могу сказать, работает до C++11 и в C++11.
Конечно это libstdc++
конкретное решение, оно выглядит действительным в libc++
также, но если вам нужна поддержка компилятора, где это не работает, вам, вероятно, придется прибегнуть к использованию #if/#else
,
Обратите внимание, как указано ММ, имеющий isnan
отмеченный constexpr является несоответствующим, это также известная проблема, хотя она не способствует этой конкретной проблеме.
Также смотрите связанные сообщения об ошибках: [C++11] вызов перегруженного isnan неоднозначен и распознает встроенные функции с типом возврата bool. Второй обсуждает возможные libstdc++
решения.
Обновить
Если вам нужно решение gcc/clang, похоже, они оба поддерживают __builtin_isnan
см. gcc docs на buildins для получения дополнительной информации. Также смотрите этот отчет об ошибке glibc по замене isnan et al на встроенную.
Подумав немного об этом, я не думаю, что есть вообще какой-либо переносимый способ сделать это до C++11. C isnan
макрос был введен в C99, но C++98 и C++03 основаны на C89. Так что, если вы полагаетесь на свою реализацию C++98/03, перетащите заголовок C99, который обеспечивает isnan
(что, кстати, не соответствует), вы все равно делаете непереносимые предположения.
Замена unsing
директива с using
Затем объявление предоставляет вам следующий код, который является переносимым C++11 (также работает с дефектом libstdC++) и может работать для более ранних реализаций со скрещенными пальцами. (Независимо от того, предоставляют ли они isnan
в качестве макроса или функции в глобальном namespace
.)
template <typename T>
bool
my_isnan(const T x)
{
#if __cplusplus >= 201103L
using std::isnan;
#endif
return isnan(x);
}
Завершение этого в свою собственную функцию, кажется, делает #if
приемлемый.
Сделать свой собственный:
bool isNaN(double x) {
return x != x;
}
Ошибки указывают на то, что у вас есть isnan в глобальном пространстве имен, а другой - в пространстве имен std. "Использование пространства имен std;" вызывает двусмысленность между теми.
Не слишком элегантно, но следующее может работать для ваших заявленных требований.
// drop 'using namespace std;'
#ifndef isnan
using std::isnan;
#endif
[РЕДАКТИРОВАТЬ] Вышесказанное относится к части первоначального вопроса об избежании двусмысленности между макросом isnan и std::isnan. Если в глобальном пространстве имен существует 3 конфликтующий:: isnan, то технически он будет описан ниже, но это еще уродливее и хрупче.
// drop 'using namespace std;'
#ifndef isnan
#define isnan(x) std::isnan(x)
#endif
[РЕДАКТИРОВАТЬ #2 ] В ответ на комментарий о "не удалось скомпилировать на C++98, в котором не определен макрос (это нормальная функция в глобальном пространстве имен), а также нет isnan(double&) в Пространство имен std"... Нечто подобное может работать в идеальном мире.
#ifndef isnan
#if __cplusplus <= 199711L // c++98 or older
# define isnan(x) ::isnan(x)
#else
# define isnan(x) std::isnan(x)
#endif
#endif
В реальном мире, однако, компиляторы имеют разные правила для __cplusplus, которые весьма противоречивы. Для более общего обсуждения и ответов я остановлюсь на том, как сделать переносимую функцию isnan/isinf.