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

■実験コードを作る

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 が素直かと。

 

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