bool値をインクリメントする……? – Togetter
http://togetter.com/li/356718
c++ – bool operator ++ and — – Stack Overflow
http://stackoverflow.com/questions/3450420/bool-operator-and
3.2 Increment and decrement [expr.pre.incr]
1 The operand of prefix ++ is modified by adding 1, or set to true if it is bool (this use is deprecated).
The operand shall be a modifiable lvalue. The type of the operand shall be an arithmetic type or a pointer
to a completely-defined object type. The value is the new value of the operand; it is an lvalue. If x is not
of type bool, the expression ++x is equivalent to x+=1. [Note: see the discussions of addition (5.7) and
assignment operators (5.17) for information on conversions. ]
を見て「えーッ!!! インクリメントすれば true/false を繰り返すほうが対称性が高いし、他との互換性も高いッ!!! 当然の規約だろう。昔 bool 型が int で実装されていたのはマシンの制約に関わる部分が多くて、コーディング的には、true/false が交互になるほうがよい」というのをぴきーんと考えたのですが…タイトルを見て分かるように「こじつけ」です。
実は手元にある VC++2010, VC++2012, G++4.5.2 で試したところ、bool をインクリメントすると true(1) -> ture(1) なんですわー、というわけで「非推奨」ですね、つーか、処理系に依存するコードを書いちゃダメですね。
■実験コードを作る
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | int main( void ) { bool b = false ; // 後置型 cout << "bool: " << b++ << endl; // false cout << "bool: " << b++ << endl; // true cout << "bool: " << b << endl; // false cout << endl; b = false ; // 前置型 cout << "bool: " << ++b << endl; // true cout << "bool: " << ++b << endl; // false cout << "bool: " << b << endl; // true cout << endl; b = false ; for ( int i=0; i<5; i++ ) { cout << b << endl; b = !b ; // 反転 } cout << endl; // 利点としてはインクリメントを使うと1行で書ける b = false ; for ( int i=0; i<5; i++ ) { cout << b++ << endl; // 後でインクリメント } cout << endl; return 0; } |
期待するところは、コメントにある通り true/false を繰り返します。
期待通りに動くのであれば、「!b」で反転させるよりも、b++ にすると1行で済むことが分かります。
bool 値は、一見「論理式」のために用意されている on/off の状態を示すように見えますが、。反転を表す「!」演算子を使う場合 on <-> off の交互になる、つまりは、
- on であれば off になる
- off であれば on になる
のようになります。しかし bool 値を一般的な int 型と考えてみると、int型のように 0 -> 1 -> 2 -> … -> MAX -> 0 のように最大値を過ぎたら 0 に戻るとうのが普通なのです。となると、0,1 の値しか持たない=1ビットで表すことができる数値として bool をとらえると、
- 0 に 1 を加えて 1 になる
- 1 に 1 を加えて 0 になる
という論法になります。この考え方は、欧米の家電のスイッチが「0」と「1」になっていることから分かりますね。
- スイッチが入っいない状態=0の状態
- スイッチが入っている状態=1の状態
という訳です。
■3値のクラスを作る
さて、この論法を確認するために 3値のクラスを考えてみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | // 例えば3値のクラスを作る class Value { int _v; public : Value() : _v(0) {} // 前置 Value& operator ++() { if ( ++_v >= 3 ) _v=0; return * this ; } // 後置 Value operator ++( int ) { Value v; v._v = this ->_v; if ( ++_v >= 3 ) _v=0; return v; } // int型へキャスト operator int () { return _v; } }; ostream& operator << ( ostream &s, Value v ) { switch (( int )v) { case 0: s << "one" ; break ; case 1: s << "two" ; break ; case 2: s << "three" ; break ; } return s; } |
0, 1, 2 の3つの値を取る整数値の場合には、++演算子は普通に使える、というのが期待されます。
1 2 3 4 5 6 7 | // 3値クラスを使う Value v ; cout << v++ << endl; // one cout << v++ << endl; // two cout << v++ << endl; // three cout << v++ << endl; // one cout << endl; |
これは期待通りに、one -> two -> three -> one のように動きます。
チェックボックスの値も、unenabled -> off -> on -> unenabled のように動くことが期待できるわけです(まあ、内実がint型だからというのもありますが)。
■任意の値を最大値とするテンプレートを作る
3値ではなくて、任意の値「MAX」が取れるようにテンプレートクラスにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | // 任意の値までのテンプレートクラス template< int MAX> class TValue { int _v; public : TValue<MAX>() : _v(0) { } // 前置 TValue<MAX>& operator ++() { if ( ++_v >= MAX ) _v=0; return * this ; } // 後置 TValue<MAX> operator ++( int ) { TValue<MAX> v; v._v = this ->_v; if ( ++_v >= MAX ) _v=0; return v; } // int型へキャスト operator int () { return _v; } }; |
この場合も期待通りに動きます。MAX の次が 0 なので、サイクリックな値として使えますね?
1 2 3 4 5 6 | TValue<3> v3 ; cout << v3++ << endl; // one cout << v3++ << endl; // two cout << v3++ << endl; // three cout << v3++ << endl; // one cout << endl; |
■Bool へ typedef する
先のテンプレートに MAX=2 を指定すると、ほら、規約にある bool と同じ動作をしますね。
1 2 3 4 5 6 | typedef TValue<2> Bool; // 2値のBoolクラス Bool bb; cout << bb++ << endl; // 0 cout << bb++ << endl; // 1 cout << bb++ << endl; // 0 cout << endl; |
つまりは、bool 型というのは、true/false という特別な型ではなくて、int 型や char 型と同じように「最大値を指定してサイクリックにインクリメントできる型」の特殊なものして、定義できる訳です。これはなんか「数学的」で綺麗でいいですよね。
■で、最初に戻って bool 型をインクリメントすると
さて、ここまで薀蓄を含めて bool 値の考察をしていきましたが、実装はどうなっているでしょうか?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | bool b = false ; // 後置型 cout << "bool: " << b++ << endl; // false cout << "bool: " << b++ << endl; // true cout << "bool: " << b << endl; // false cout << endl; b = false ; // 前置型 cout << "bool: " << ++b << endl; // true cout << "bool: " << ++b << endl; // false cout << "bool: " << b << endl; // true cout << endl; |
の結果は、VC++2010, VC++2012 で実行すると、
1 2 3 4 5 6 7 | bool : 0 bool : 1 bool : 1 bool : 1 bool : 1 bool : 1 |
あーあー、うん、あーあ、どうでもいいや、ってな気分です。
# ちなみに、VC++2010 の場合はデクリメントをしようとするとコンパイルエラーになります。
処理としては、0 -> 1, 1 -> 1 という実装みたいですね。
もしデクリメントを実装するとすると、1 -> 0, 0 -> 0 が素直かと。