【问题标题】:Why is isnan ambiguous and how to avoid it?为什么 isnan 模棱两可,如何避免?
【发布时间】:2016-02-19 14:48:35
【问题描述】:

由于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)(x);还是报错,那就和宏无关
  • 我在 Windows 的 g++ 4.9.2 中没有得到错误,但是在使用某种 Linux 的 Godbolt 上得到了它。所以也许这是 glibc 头文件中的一个错误
  • std::isnan(long double)std::isnan(float) 也被列出了一些错误,因为它们无论如何都不能被选中;歧义在::isnan(double)std::isnan(double) 之间
  • @JohnDrouhard 好点,真正的错误是有两个不同的功能。 C++ 标准要求如果存在::isnan,则std::isnan 必须是注入命名空间std 的相同函数。 C++ 库应该使用 C 库的 isnan(double) 并仅添加 std::isnan(long double)std::isnan(float)

标签: c++ c++11 gcc compiler-errors cmath


【解决方案1】:

这是错误报告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;
}

其中一位 cmets 说:

我不认为这是问题所在,因为 libstdc++ 总是在全局命名空间中声明名称,即使它在 C++03 中无效——我们还没有为 C++0x 更改它(所有这些发生的事情是放宽了标准以反映实际实施的现实)

这可能最终会得到解决,直到那时错误报告中提供的解决方案如下:

通过调用 ::isnan 或 std::isnan 显式限定 isnan

据我所知,使用::isnan 可以工作pre C++11in C++11

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

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

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

更新

如果您想要 gcc/clang 解决方案,它们看起来都支持__builtin_isnan,请参阅gcc docs on builtins 了解更多信息。另请参阅此 glibc bug report 将 isnan et al 替换为内置函数。

【讨论】:

  • 我认为引用的评论忽略了问题。问题是标头声明了两个不同的函数::isnan(double)std::isnan(double)。该评论似乎在谈论::foostd::foo 之间的(非)歧义,在::foo 的名称被标头注入std 的情况下(从C++ 开始明确允许) 11).
  • @M.M 同意,它更复杂,但包括所有 cmets 将是一个很长的答案。
【解决方案2】:

在考虑了一段时间之后,我认为在 C++11 之前根本没有可移植的方式来做到这一点。 C isnan 宏是在 C99 中引入的,但 C++98 和 C++03 基于 C89。因此,如果您依赖 C++98/03 实现来拖入提供 isnan 的 C99 标头(顺便说一句,这是不符合标准的),那么您无论如何都在做出不可移植的假设。

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 可以接受。

【讨论】:

    【解决方案3】:

    自己制作:

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

    【讨论】:

    • 请注意,如果您使用 --ffast-math 编译器标志,这将不起作用。除此之外,如果你想要一些真正便携的东西,我认为这是最好的(唯一的?)选项
    • 如果内容是return std::isnan(x)这将是一个很好的答案
    • @M.M.如果有 isnan 的宏形式在起作用,return std::isnan(x) 可能会扩展为像 return std::((x) != (x)); 这样破碎的东西
    • @TonyD 好的,(std::isnan)(x)
    • 如果它是一个 signalling NaN,这可能效果不太好?
    【解决方案4】:

    错误表明您在全局命名空间中有一个 isnan,而在 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
    

    [ EDIT #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

    【讨论】:

    • 您对问题的分析是正确的,但建议的修复方法不起作用。因为Glibc中没有isnan宏。
    • @5gon12eder 我没有在这里尝试,也没有设置。该答复适用于原始问题中关于避免宏 isnan 和 std::isnan 之间歧义的部分。如果全局命名空间中存在第三个冲突的 ::isnan(似乎是这种情况),那么这确实没有被涵盖。
    • 不幸的是,您的编辑仍然无法在 C++98 上编译,它没有定义宏(它是全局命名空间中的普通函数),也没有 isnan(double&amp;) 在标准命名空间。
    • @dxiv 最初的问题是关于两个函数之间的歧义。 OP提到“宏观”是不正确的猜测
    • @JohnDrouhard 你是对的,我为此添加了一条注释。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-03
    • 1970-01-01
    • 2021-11-28
    相关资源
    最近更新 更多