私はこの質問を読んでいた:
そして、具体的には、 C++ 11 answer であり、評価の「順序付け」のアイデアを理解しています。しかし、-私が書いたときに十分なシーケンスがあります:
f(x++), g(x++);
?
つまり、f()
がx
の元の値を取得し、g()
が一度インクリメントされたx
を取得することを保証していますか?
Nitpickersに関する注意:
operator++()
が動作を定義していると仮定します(オーバーライドした場合でも)f()
とg()
を行い、例外がスローされない、など-この質問はそれについてではありません。operator,()
がオーバーロードされていないと仮定します。いいえ、動作は定義されています。 C++ 11(n3337)を引用するには [expr.comma/1] :
コンマで区切られた式のペアは、左から右に評価されます。左の式は破棄された値の式です(Clause [expr])。 左式に関連付けられたすべての値の計算と副作用は、右式に関連付けられたすべての値の計算と副作用の前にシーケンスされます。
そして、私は「すべて」を「すべて」を意味する1。 2番目のx++
の評価は、f
への呼び出しシーケンスが完了してf
が返るまで発生しません。2
1 デストラクタ呼び出しは部分式には関連付けられず、完全な式にのみ関連付けられます。したがって、完全な式の最後に、一時オブジェクトの作成とは逆の順序で実行されたものが表示されます。
2 この段落は、演算子として使用される場合にのみコンマに適用されます。コンマに特別な意味がある場合(関数呼び出し引数シーケンスを指定する場合など)、これは適用されません。
この評価順序と順序付けリファレンス によると、コンマの左側は右側の前に完全に評価されます( rule 9を参照):
9)組み込みコンマ演算子の最初の(左)引数のすべての値の計算と副作用は、2番目(右)の引数のすべての値の計算と副作用の前に順序付けられます。
つまり、f(x++), g(x++)
のような式はnot未定義です。
これは、built-inコンマ演算子に対してのみ有効であることに注意してください。
まず、x++
自体は未定義の動作を呼び出さないと仮定しましょう。符号付きオーバーフロー、過去のエンドポインターのインクリメント、またはpostfix-increment-operatorがユーザー定義である可能性について考えてください)。
さらに、f()
およびg()
を引数で呼び出して一時ファイルを破棄しても、未定義の動作は呼び出されないと仮定します。
これは非常に多くの仮定ですが、それらが壊れている場合、答えは簡単です。
ここで、コンマが組み込みのコンマ演算子、braced-init-listのコンマ、またはmem-initializer-listのコンマである場合、左側と右側が互いに前後に配列されます(そしてあなたはどちらを知っているか)、干渉しないで、動作を明確に定義してください。
struct X {
int f, g;
explicit X(int x) : f(x++), g(x++) {}
};
// Demonstrate that the order depends on member-order, not initializer-order:
struct Y {
int g, f;
explicit Y(int x) : f(x++), g(x++) {}
};
int y[] = { f(x++), g(x++) };
それ以外の場合、x++
がpostfix-incrementに対してユーザー定義の演算子オーバーロードを呼び出すと、x++
の2つのインスタンスのシーケンスが不確定になり、動作が指定されなくなります。
std::list<int> list{1,2,3,4,5,6,7};
auto x = begin(list);
using T = decltype(x);
void h(T, T);
h(f(x++), g(x++));
struct X {
X(T, T) {}
}
X(f(x++), g(x++));
最後のケースでは、x
の2つの後置インクリメントがシーケンスされていないため、完全に未定義の動作になります。
int x = 0;
void h(int, int);
h(f(x++), g(x++));
struct X {
X(int, int) {}
}
X(f(x++), g(x++));