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

■実験コードを作る

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

 

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