Part 4.変数の定義域(4)

プログラのたみ!


前回、ループの条件は後判定になることが多いということについて書いた。


いま、次の列挙体について考えてみよう。

enum A { x,y,z };

列挙体Aすべての要素に対して何らかのアクションを行ないたいとする。foreach構文のようなものがあれば良いのだが、不運にもいま対象としている言語には無いとしよう。しかし、最初と最後の要素に関しては、A.begin , A.end というような alias が使えると仮定しよう。この場合、次のように前判定で書く必要がある。


A a = A.begin;
do {
do_something();
} while (a != A.end && ++a)

非常に読みづらい。doとwhileが(画面外ぐらいまで)離れていると、whileのところを見ないと、このループが何なのかすら理解できないだろう。またこのように複文を&&を用いて変形してwhileの条件式のところに書くのもあまり一般的なイデオムとは言えない。素直に以下のように書くほうが他の人に伝わりやすいコードだ。


A a = A.begin;
while(true) {
do_something(a);
if (a == A.end) break;
++a;
}

しかし、上のようなコードだと、Aが空集合の場合どうなるのだろうか。Aが空集合の場合、「A.begin == A.end == A.illegal(不正な要素)」という関係式が成り立つとして、この場合、上のコードだとdo_something(A.illegal);が一回実行されてしまう。これは、論理的におかしい。


以上を加味すると、(C言語では)列挙体に対して、デリミタを次のように用意することによって後判定に書き換えることが多いのもうなづける。


enum A { x,y,z,delimiter };

for( A a = A.begin ; a != A.delimiter ; ++a)
do_something(a);

本来のこの列挙体の集合の要素はx,y,zだけであり、delimiterというのは、この集合の外の要素を仮想的に導入したものである。この集合外の要素を変数aが取れるようにすることによって、ループを後判定に書き換えられるのである。こう書けば、Aが空集合であっても、do_something(A.illegal)は実行されない。C++で、STLiteratorのend()が、終端の次の要素を指すようになっているのはこのような理由からだ。


C/C++において、前判定で条件を書くための制御構文が不足しているというのは確かにそうだ。それゆえ、後判定で書けるように工夫すべきだというのもそうだろう。そういう、C言語のセマンティックな特徴を度外視して考えたとしても、やはり(前判定よりは)後判定で書くほうが良いと思うだけの根拠がある。それについて考察していくとしよう。(つづく)