Почему 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.

Другие вопросы по тегам