web-dev-qa-db-ja.com

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の両方で機能させる方法を教えてください。

11
vitaut

これはバグレポートに記載されているlibstdc++バグです c ++ 0xサポート(および名前空間stdを使用)でビルドする場合、std関数はC関数と競合します 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;
}

そしてコメントの1つは言う:

Libstdc ++は、C++ 03では有効ではありませんでしたが、常にグローバル名前空間で名前を宣言しているため、これは問題ではないと思います。C++ 0xでは変更していません(発生したのは標準は実際の実装の現実を反映するために緩和されました)

これは最終的に修正される可能性があり、それまではバグレポートから提供される解決策は次のとおりです。

:: isnanまたはstd :: isnanのいずれかを呼び出して、isnanを明示的に修飾します。

私が知る限り、::isnanを使用すると、 C++ 11以前 および C++ 11 で動作します。

もちろん、これはlibstdc++固有のソリューションであり、libc++でも有効に見えますが、これが機能しないコンパイラをサポートする必要がある場合は、おそらく#if/#elseを使用する必要があります。 。

isnanとマークされたconstexprを持つM.Mが示すように、これは不適合です。これは 既知の問題でもあります ですが、この特定の問題には寄与しません。

関連するバグレポートも参照してください: [C++ 11]オーバーロードされた「isnan」の呼び出しはあいまいです および ブール戻り値の型でビルトインを認識します 。 2つ目は、考えられるlibstdc++ソリューションについて説明します。

更新

Gcc/clangソリューションが必要な場合は、どちらも__builtin_isnanをサポートしているようです。詳細については、 組み込みのgccドキュメント を参照してください。 isnan et alを組み込みに置き換える方法については、このglibc バグレポート も参照してください。

12
Shafik Yaghmour

これについてしばらく考えた後、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許容できる。

5
5gon12eder

自分で作る:

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

エラーは、グローバル名前空間にisnanがあり、std名前空間に別のisnanがあることを示しています。 「usingnamespacestd;」それらの間にあいまいさを引き起こします。

あまりエレガントではありませんが、以下はあなたの述べた要件に合うかもしれません。

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

[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のさまざまなルールがあり、これらはまったく一貫性がありません。より一般的な議論と回答については、以下を参照します ポータブルisnan/isinf関数を作成するにはどうすればよいですか

2
dxiv