読者です 読者をやめる 読者になる 読者になる

intとunsigned int のキャスト検証

C++ プログラミング

サークルで以下のようなC++のコード(一部改変。)が話題になった

	int hoge=-1;
	if (hoge<=strlen("hoge")){
		std::cout<<"Hoge"<<std::endl;
	}else{
		std::cout<<"Bar"<<std::endl;
	}

実行してみればわかると思うが、else節の方が実行されてしまう。なぜだろうか?
少し情報科学(といえない程度のものだが)をかじった普通のプログラマならすぐに符号ビットの問題であることに気づくだろう。ましてや二年もかけてCをみっちり勉強した情報系学部のそれもコンピュータ系サークルの人ならきっと即答なはずだ。皮肉はさておき。
 実はこれが本質的にキャストの問題であると気づいたのはしばらく考えてからだ。検証コードを考えるのにさらに時間がかかってしまった。

#include<iostream>

template<typename T>
void func(){
	std::cout<<"Other type"<<std::endl;
}

template<>
void func<int>(){
	std::cout<<"Type is int"<<std::endl;
}

template<>
void func<unsigned int>(){
	std::cout<<"Type is unsigned int"<<std::endl;
}


int main(){
	int hoge=-1;
	unsigned int foo=4;

	func<decltype(hoge+foo)>();

	return 0;
}

これを実行すればわかるようにhogeとfooを足した値はunsigned intにキャストされていることがわかる。まあ、実行時型情報を使ってもよかったんだろうが、コンパイル時にわかってしまうのでdecltypeとテンプレートの特殊化で対応してしまった。
 仕様書を読んだわけではない(めんどうでした。ごめんなさい)が正しい動作であることは以下を考えればわかる。まず、singnedとunsignedはサイズが同じである(unsignedは符号ビットを使わずその分大きな数を表現できるようになっているためサイズは同じ)ということ。これから通常のサイズの大きな型に合わせるというキャストのメカニズムがうまくいかない。したがってどちらかにキャストするかあるいはコンパイルエラーを吐くかが妥当だろう。(VSでは警告を出すことによって対応している。ちなみに今回のサークルの問題では警告はきっておいた二回目以降のビルドでは普通に警告が出ないらしいです。クリーンビルドで警告が再び出るようになります)
C++の「C++はもしプログラマが間違っている可能性があったとしてもプログラマに選択の余地を与える。」という考え方からは最大限プログラマの書いたとおりに動くのが妥当だろう。ここでおける仮定の可能性は以下の2つがある。
1.intの変数には自然数(0を含む)が入っている。
2.unsigned intの変数には2^31以下の値が入っている
どちらの仮定がよりbetterだろうか?答え(少なくともVSの)としては1である。自然数がはいっていることは(プログラマが論理的に正しい思考をできているなら)式からして当然だが、unsignedの値が十分に小さいかどうかはコンテクストに依存するので仮定できない。ちなみにintにキャストしたにせよどうせ大きな数が入っていた場合はマイナスとして扱われてしまう。
ただこの検証コードを見てわかるように足し算でもunsigned intにキャストされてしまうのだ。これは予想外の動作をしかねないと思うので注意してほしい。