未定義動作となる式
やっぱりまだいまいち掴めてない感じがするので関連項目を体系的に全部理解出来たと思えた頃にまた書きます。
理解不足で間違った事を書いていて誤解を生んでしまいかねないので。
更に追記
If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
未定義動作になるのは同じオブジェクトに対する計算の『値』を用いた場合。
@fimbul11 はい.value computationとside effectがunsequencedでもvalue computationの値を使わなければセーフです.複合代入は値を見る必要があるのでアウトです
— あめだま (@amedama41) December 12, 2013
a = a = a; // well-defined a += a+= a; // undefined
アドレスを用いる代入の連結はwell-defined、値を用いる複合代入の連結はundefinedという考え方。
C++11以降のルール、『sequence before』を規格から正しく解釈理解するのはとても難しいです…。
追記
@fimbul11 C++11 から代入に追加のシーケンス規定が明記されたことで、同じオブジェクトへの代入をつなげても今は問題なくなっているのでは? 5.17 p1 "the assignment is sequenced after ... and before ..."
— Kazutoshi SATODA (@k_satoda) December 12, 2013
@fimbul11 参考: http://t.co/JlMM0Op1YX "5.17p1: Assignment" "Also note that I have added a new constraint: ..." DR #637 http://t.co/ttcztz9OC1
— Kazutoshi SATODA (@k_satoda) December 12, 2013
この指摘の箇所を参照すると
i = ++i + 1; // well-sequenced
のようなものはC++03ではundefinedでしたがC++11以降well-definedといえます。
Twitterでかりやさんにツッコミを受けて式が未定義動作になる条件を規格から調べ直したのでメモしておきます。
自分も勘違いしていたので、かりやさんには感謝です。
参照したドラフトはN3797
5.17 Assignment and compound assignment operators
The assignment operator (=) and the compound assignment operators all group right-to-left. All require a modifiable lvalue as their left operand and return an lvalue referring to the left operand.
の項に、次の演算子ら= *= /= %= += -= >>= <<= &= ˆ= |= は全て右結合性を持つことが定義されています。
そこで以下のコードは次のように解釈されます。
int a = 1; int b = 2; a += b; // (a += b); a += b += a; // (a += (b += a)); a += b += a += b; // (a += (b += (a += b)));
次に1.9 Program execution 15項を参照すると
Except where noted, evaluations of operands of individual operators and of subexpressions of individual expressions are unsequenced. [ Note: In an expression that is evaluated more than once during the execution of a program, unsequenced and indeterminately sequenced evaluations of its subexpressions need not be performed consistently in different evaluations. — end note ] The value computations of the operands of an operator are sequenced before the value computation of the result of the operator. If a side effect on a scalar object is unsequenced relative to either another side effect on the same scalar object or a value computation using the value of the same scalar object, the behavior is undefined.
強調部分によると、特に評価順に断りがない限り式中で同一要素に関して複数回の副作用を伴う場合に、式の評価が未定義動作となるとのことです。カンマ演算子等が、この例外に含まれます。
5.18 Comma operatorから引用すると
Every value computation and side effect associated with the left expression is sequenced before every value computation and side effect associated with the right expression.
副作用の評価が必ず左側の項から順に行われると厳密に規定されているので、そのような順番に依存するコードを書いても問題ないのです。 1.9.15にもそのような例が記載されています。
i=7,i++, i++; // i becomes 9
もう1度先程のコードを考えると
int a = 1; int b = 2; a += b; // aに対する副作用を1回含む a += b += a; // a, bに対する副作用を1回ずつ含む a += b += a += b; // aに対する副作用を2回含む // それに加えてbに対する副作用を1回含む
このコード例では3番目の式だけは未定義動作となります。
一見、演算子の右結合性から必ず(a += (b += (a += b)))と解釈されるので、結果は一意に定まるように思えるのですが、同じ要素に対する副作用を複数回含んでいる時点で未定義動作なのです。
a += b += a += b; // 未定義動作
逆に同じオブジェクトに対して複数回の副作用を伴わないので以下のようなコードは未定義動作ではありません。
int a = 1; int b = 2; int c = 3; a += b += c += 1;