移植性の高いプログラムの書き方
「forループ変数のスコープ」編

しのはらのC++実験室トップページ
「移植性の高いプログラムの書き方」トップページ

forのループ変数

 C++ではfor構文の先頭で次のようにループ変数を宣言することができます。
for(int i=0; i<10; ++i ){
	/* .... */
}
これは「変数は使う場所で宣言」というC++の考え方を実践している文法と言えます。 また、ここで宣言しているint型変数iのスコープ(つまり有効範囲)はforループ内部に限定されています。 forを抜けたあとではiを使うことはできません。 これがC++の仕様です。 このように、iはまさにforループのための変数なので、 forループが始まると同時に必要となり、 forループが終わったあとでは必要のないものとなるという意味論を 反映した合理的な仕様であるといえます。

ループ変数に関する古いC++仕様

 ところがC++の古い仕様では、事情が少し違っていました。 forループ変数のスコープはループの内部に限定されていず、 ループを抜けたあともループ変数が有効という仕様になっていました。 つまり、以下のコードではiの二重定義エラーになっていました。
for(int i=0; i<10; ++i ){
	/* .... */
}
/* .... */
for(int i=0; i<10; ++i ){
	/* .... */
}
 この事実は、古いC++の仕様に基づくコンパイラとそうでないコンパイラの間で コードを移植する際に問題となります。

お勧めできない解決方法

 この問題を解決するには2つのやり方があります。 そのうちのお勧めの方法を紹介する前に、誰もが考える、しかしお勧めできない方法を示します。このお勧めできない方法とは消極的な方法であり、古い慣習に従うコードを書くというものです。
int i;
for(i=0; i<10; ++i ){
	/* .... */
}
/* .... */
for(i=0; i<10; ++i ){
	/* .... */
}
 この方法では「変数は使う場所で宣言」という、 コードを読みやすくするための経験的ルールに反します。 またループの外部でもループ変数が生きているため、 誤ってループ外部で使用してしまう間違いをコンパイラに指摘させることができません。

お勧めの解決方法

 ループ変数のスコープ問題を解決する、最もスマートと僕が考える方法を紹介します。 それは、次のようなマクロ定義をすることです。
#define for if(0);else for
 このように定義されたforを使用すれば、ループ変数のスコープに関してコンパイラがどちらの意味論に従っているかに関係なく、同じ結果を得ることが出来ます。 しかもそれは古くないC++の仕様に従うものです。 つまりこのマクロ定義を使えば、安心して最新仕様に基づくループ変数を使用できるようになるのです。

ちょっとNGな方法

 さてこのようにマクロ定義によってforループ変数のスコープ問題を回避するアイデアは友人の北田氏から教えられたものです。元々の彼のアイデアはこうでした。
#define for if(1)for
これだとfor文の直後にelseが来る場合に、間違ったコードを生成します。 そこで僕は最初、次のように修正しました。
#define for switch(0)default:for
これは期待通りに機能します。しかしコードの質にこだわる人にとって少しだけ問題がありました。一部のコンパイラではこのような明らかに無用かつ無害な構文のためにさえ若干の無駄なコードを生成してしまうのです。 すなわちswitch構文を使用することによるコストが生じてしまうのです。
 この問題は「お勧めの解決方法」で示したようにif(0);elseという、switchに比べてより複雑さの少ない方法を使用することで、解決できました。