[C++] bool値をインクリメントすると、ture/false を繰り返す理由…をこじつける

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) なんですわー、というわけで「非推奨」ですね、つーか、処理系に依存するコードを書いちゃダメですね。

■実験コードを作る

[code lang=”csharp”]
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;
}
[/code]

期待するところは、コメントにある通り 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値のクラスを考えてみましょう。

[code lang=”csharp”]
// 例えば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;
}
[/code]

0, 1, 2 の3つの値を取る整数値の場合には、++演算子は普通に使える、というのが期待されます。

[code lang=”csharp”]
// 3値クラスを使う
Value v ;
cout << v++ << endl; // one
cout << v++ << endl; // two
cout << v++ << endl; // three
cout << v++ << endl; // one
cout << endl;
[/code]

これは期待通りに、one -> two -> three -> one のように動きます。
チェックボックスの値も、unenabled -> off -> on -> unenabled のように動くことが期待できるわけです(まあ、内実がint型だからというのもありますが)。

■任意の値を最大値とするテンプレートを作る

3値ではなくて、任意の値「MAX」が取れるようにテンプレートクラスにします。

[code lang=”csharp”]
// 任意の値までのテンプレートクラス
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;
}
};
[/code]

この場合も期待通りに動きます。MAX の次が 0 なので、サイクリックな値として使えますね?

[code lang=”csharp”]
TValue<3> v3 ;
cout << v3++ << endl; // one
cout << v3++ << endl; // two
cout << v3++ << endl; // three
cout << v3++ << endl; // one
cout << endl;
[/code]

■Bool へ typedef する

先のテンプレートに MAX=2 を指定すると、ほら、規約にある bool と同じ動作をしますね。

[code lang=”csharp”]
typedef TValue<2> Bool; // 2値のBoolクラス
Bool bb;
cout << bb++ << endl; // 0
cout << bb++ << endl; // 1
cout << bb++ << endl; // 0
cout << endl;
[/code]

つまりは、bool 型というのは、true/false という特別な型ではなくて、int 型や char 型と同じように「最大値を指定してサイクリックにインクリメントできる型」の特殊なものして、定義できる訳です。これはなんか「数学的」で綺麗でいいですよね。

■で、最初に戻って bool 型をインクリメントすると

さて、ここまで薀蓄を含めて bool 値の考察をしていきましたが、実装はどうなっているでしょうか?

[code lang=”csharp”]
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;
[/code]

の結果は、VC++2010, VC++2012 で実行すると、

[code lang=”csharp”]
bool: 0
bool: 1
bool: 1

bool: 1
bool: 1
bool: 1
[/code]

あーあー、うん、あーあ、どうでもいいや、ってな気分です。

# ちなみに、VC++2010 の場合はデクリメントをしようとするとコンパイルエラーになります。

処理としては、0 -> 1, 1 -> 1 という実装みたいですね。

もしデクリメントを実装するとすると、1 -> 0, 0 -> 0 が素直かと。

 

カテゴリー: C++ パーマリンク