式がひとつしかかけない文脈に複数の式をかきたいときにコンマ演算子を使う.
典型的な使い方は次のようなもの.
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; }
comma0.cpp
comma0b.cpp
このプログラム例では,配列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);
comma.cpp
普通こんなことを書く人はいないと思うので,たいして問題にはならないかもしれないけど, 演算子の優先順位を気にせずに下のようなプログラムを書くと混乱するかもしれない.
int a = 1, b = 3, result; result = ++a, a + b; cout << result << endl;
コンマ演算は,(1)左から右に評価されて,(2)式の値は右の式の値になる…という規則だったので,
まず++a
を評価してa
が2
になり,
次にa + b
→2 + 3
を評価して5
なので,
result
は5
になると考えてしまいがちだけど,
実はこの式は(result = ++a), a + 5
の順に評価されるので,result
は2
になるというのが正解.
result
を5
にしたいつもりなら,演算順序をかえるため,result = (++a, a + b);
と書かないといけない.