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) なんですわー、というわけで「非推奨」ですね、つーか、処理系に依存するコードを書いちゃダメですね。
■実験コードを作る
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値のクラスを考えてみましょう。
// 例えば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つの値を取る整数値の場合には、++演算子は普通に使える、というのが期待されます。
// 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」が取れるようにテンプレートクラスにします。
// 任意の値までのテンプレートクラス 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 なので、サイクリックな値として使えますね?
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 と同じ動作をしますね。
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 値の考察をしていきましたが、実装はどうなっているでしょうか?
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 で実行すると、
bool: 0 bool: 1 bool: 1 bool: 1 bool: 1 bool: 1
あーあー、うん、あーあ、どうでもいいや、ってな気分です。
# ちなみに、VC++2010 の場合はデクリメントをしようとするとコンパイルエラーになります。
処理としては、0 -> 1, 1 -> 1 という実装みたいですね。
もしデクリメントを実装するとすると、1 -> 0, 0 -> 0 が素直かと。