コンマ演算(comma operation)

式がひとつしかかけない文脈に複数の式をかきたいときにコンマ演算子を使う.

典型的な使い方は次のようなもの.

const int n = 5;
int a[n] = { 1, 2, 3, 4, 5 }, tmp;
for (int *head = a, *tail = a + n - 1; head < tail; ++head, --tail) {
    tmp = *head;
    *head = *tail;
    *tail = tmp;
}

このプログラム例では,配列aの要素を{1, 2, 3, 4, 5}{5, 4, 3, 2, 1}と逆順に並べなおしている. for文の3番目の式の“++head, --tailの部分がコンマ演算で,このように書くと, まずコンマの左側の式“++head”を評価し,続いてコンマの右側の式“++tail”を評価できるようになる. [この例では無理にコンマ演算子を使って++head++tailを1個の式にまとめる必要はない(普通にwhile文で書いてもよい)のだけども, ときどきコンマ演算で式をつなぐとわかりやすくなったりすることがある.] [初期化式“int *head = a, *tail = a + n -1”の部分はコンマ演算ではなく,変数の定義と初期化にあたっている(はず). 変数定義ではもともとint a, b;みたいにコンマで区切って複数の定義を並べることができるので,コンマ演算とは関係ない(はず).]

コンマ演算の文法および規則

式::=
    代入式
    式 , 代入式

コンマ演算は左から右の順で評価される.コンマの左側の式の値は破棄され,右側の式の値がコンマ演算式全体の値になる. コンマ演算の結果の型と値は,右側のオペランドの型と値になる.

コンマ演算に対しては,lvalueからrvalueへの標準変換,配列からポインタへの標準変換,関数からポインタへの標準変換は適用しない. [コンマ演算の場合,コンマの左と右には無関係な式が書かれることが多いだろうから,この仕様は(アドホックではあるけど)自然だと思う.]

左側の式のすべての副作用は,テンポラリの破棄を除き,右側の式を評価する前に実行される. [テンポラリの破棄を除くという部分はオブジェクトの生存期間を気にした表現で, 各式が基本データ型の場合は影響ないかも.例えば, “(a = 3), a + 2, a + 2”のように書いたとして,2番目の式のテンポラリオブジェクトが(int型で)5になっていたとしても, 3番目の式で,このテンポラリオブジェクトを参照する方法がないため,結局この5という値を続けて計算に使うことができないように思う.]

文脈によってはコンマは特別な意味をもっていて,コンマ演算とは解釈されない.コンマが特別な意味をもつ文脈には,例えば,以下のようなものがある.

たとえば,次のサンプルプログラムでは,関数fの呼び出しの実引数は3個,第2引数の値は5と評価される.

f(a, (b = 3, b + 2), c);

演算子の優先順位に気をつけて

普通こんなことを書く人はいないと思うので,たいして問題にはならないかもしれないけど, 演算子の優先順位を気にせずに下のようなプログラムを書くと混乱するかもしれない.

int a = 1, b = 3, result;
result = ++a, a + b;
cout << result << endl;

コンマ演算は,(1)左から右に評価されて,(2)式の値は右の式の値になる…という規則だったので, まず++aを評価してa2になり, 次にa + b2 + 3を評価して5なので, result5になると考えてしまいがちだけど, 実はこの式は(result = ++a), a + 5の順に評価されるので,result2になるというのが正解. result5にしたいつもりなら,演算順序をかえるため,result = (++a, a + b);と書かないといけない.


はたいたかし
2007-01-18
トップ > 開発ツール > C++