3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GCCのstd::isnan()に対する警告の謎

Last updated at Posted at 2019-07-01

GCCのstd::isnan()に対する警告の謎

C++/11のstd::isnan()を利用したとき、gccでわけのわからない挙動があったのでメモ書き。

動かないわけではないので無視もできたが、原因がわからず気持ち悪かったので深追いし、えらい時間を喰った。

現象

実装中、以下の現象が発生した。

環境

CentOS7上のgcc7 (DevToolkit 7)で発生。

$ gcc --version
$ gcc (GCC) 7.3.1 20180303 (Red Hat 7.3.1-5)

コード

以下コードをコンパイルすると、型変換に関するコンパイラ警告が発生する。

# include <cmath>

int main() {
  double x = 3.1;

  // warning: narrowing conversion of ‘isnan(x)’ from ‘int’ to ‘bool’ inside { } [-Wnarrowing]
  const std::vector<bool> a = {
    std::isnan(x),
  };
}
g++ -std=c++14  -Wall -Wextra -Wno-unused-variable -Wconversion test.cpp

std::isnan()の戻り値はboolのはず。なんでや!

理由

cstdlib内のisnan()の定義が、C++/11仕様と、XOPENという古い拡張仕様の2つあることが原因の模様。

Prior to GCC 6 the header declared bool std::isnan(double) and bool std::isinf(double) functions for C++11 and later, and the header (which came from the C library) declared the X/Open int ::isnan(double) and int ::isinf(double) functions. If a program added a using namespace std; to the global namespace then the two sets of functions would clash, and the program would fail to compile (but only in C++11 mode!)
~
GCC 6 now detects when the obsolete functions are present in the C header and uses them, avoiding the clash (but meaning that the function might return int instead of bool as C++11 requires)

上記環境では、問題のコードは以下のように前処理されていた(isnanに関係ない部分は除去済)。

extern "C" {
extern int isnan(double __value) throw() __attribute__((__const__));
}
extern "C++" {
namespace std __attribute__((__visibility__("default"))) {
    constexpr bool isnan(float __x) { return __builtin_isnan(__x); }
    using ::isnan;
    constexpr bool isnan(long double __x) { return __builtin_isnan(__x); }
}  // namespace std__attribute__((__visibility__("default")))
}

int main() {
  const std::vector<bool> y = { std::isnan(0.5), };
}

extern "C"{}内(グローバル名前空間)で定義されたisnan()が、usingでstd内に取り込まれている。

よって、引数がdoubleのstd::isnan()だけはintを返す挙動になっていた、ということの模様。

その他

以下だと警告が出ない。なんでや!

double x = 3.1;
// vectorではなく配列のイニシャライザに指定 (組み込み配列と挙動が違うのはまあ納得)
const bool b[] = {
  std::isnan(x),
};
// 変数ではなくリテラルをそのまま指定 (これで警告が消えるわけがわからない…)
const std::vector<bool> a = {
  std::isnan(3.1),
};

C++のすごい人がレスしてくれることを願って投稿。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?