为什么isnan有歧义,如何避免?

Why is isnan ambiguous and how to avoid it?

因为 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 中工作,最好没有太多的条件编译?

错误表明您在全局命名空间中有一个 isnan,在 std 命名空间中有另一个。 "using namespace std;" 导致它们之间的歧义。

不太优雅,但以下内容可以满足您提出的要求。

// drop 'using namespace std;'

#ifndef isnan
using std::isnan;
#endif

[ 编辑 ] 以上内容适用于原始问题中关于避免宏 isnan 和 std::isnan 之间的歧义的部分。如果在全局命名空间中存在第三个冲突的 ::isnan,那么从技术上讲,下面的内容将涵盖它,但那更丑陋、更脆弱。

// drop 'using namespace std;'

#ifndef isnan
#define isnan(x) std::isnan(x)
#endif

[编辑 #2] 回复关于“无法在 C++98 上编译,它没有定义宏的评论(它是全局命名空间中的一个普通函数),在 std 命名空间中也没有 isnan(double&)"...这样的东西可能在理想世界中有效。

#ifndef isnan
#if __cplusplus <= 199711L  // c++98 or older
#  define isnan(x) ::isnan(x)
#else
#  define isnan(x) std::isnan(x)
#endif
#endif

然而,在现实世界中,编译器对 __cplusplus 有不同的规则,这些规则非常不一致。对于更一般的讨论和答案,我将推迟到 how do I make a portable isnan/isinf function

自己制作:

bool isNaN(double x) { 
  return x != x;
}

这是错误报告 std functions conflicts with C functions when building with c++0x support (and using namespace std) 中记录的一个 libstdc++ 错误,其重现样本与 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;
}

其中一条评论说:

I don't think that's the problem, because libstdc++ has always declared the names in the global namespace even though it wasn't valid in C++03 - we haven't changed that for C++0x (all that happened is the standard was relaxed to reflect the reality of actual implementations)

此错误最终可能会得到修复,错误报告提供的解决方案如下:

Qualify isnan explicitly, by calling either ::isnan or std::isnan

据我所知使用 ::isnan pre C++11 and in C++11

当然这是一个 libstdc++ 特定的解决方案,它在 libc++ 中看起来也有效但是如果你需要支持一个编译器而这不起作用你可能不得不求助于使用#if/#else.

请注意,如 M.M 所示,具有 isnan 标记的 constexpr 是不合格的,这是一个 known issue as well 尽管它不会导致此特定问题。

另请参阅相关错误报告:[C++11] call of overloaded ‘isnan’ is ambiguous and Recognize builtins with bool return type。第二个讨论了可能的 libstdc++ 解决方案。

更新

如果您想要 gcc/clang 解决方案,看起来它们都支持 __builtin_isnan,请参阅 gcc docs on builtins for more information. Also see this glibc bug report 用内置函数替换 isnan 等人。

考虑一段时间后,我认为在 C++11 之前根本没有可移植的方法来执行此操作。 Cisnan 宏是在 C99 中引入的,但 C++98 和 C++03 是基于 C89 的。因此,如果您依靠您的 C++98/03 实现来拖入提供 isnan(顺便说一句,即 non-conforming)的 C99 header,那么您正在制作 non-portable 无论如何假设。

using 声明替换 unsing 指令然后为您提供以下可移植 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 可以接受。