カンマ付きの三項演算子が真の場合に1つの式しか評価しないのはなぜですか?


119

私は現在、C ++ Primerという本を使ってC ++を学んでいます。本の演習の1つは次のとおりです。

次の式の意味を説明してください。 someValue ? ++x, ++y : --x, --y

私たちは何を知っていますか?三項演算子はコンマ演算子よりも優先順位が高いことがわかっています。2項演算子を使用すると、これは非常に簡単に理解できましたが、3項演算子を使用すると、少し苦労します。二項演算子で「優先順位が高い」とは、式の前後に優先順位の高い括弧を使用できることを意味し、実行は変更されません。

三項演算子の場合は、次のようにします。

(someValue ? ++x, ++y : --x, --y)

コンパイラーがコードをグループ化する方法を理解するのに私を助けない同じコードを効果的にもたらします。

ただし、C ++コンパイラでのテストから、式がコンパイル:されることがわかり、演算子自体が何を表すことができるかわかりません。したがって、コンパイラーは三項演算子を正しく解釈するようです。

次に、2つの方法でプログラムを実行しました。

#include <iostream>

int main()
{
    bool someValue = true;
    int x = 10, y = 10;

    someValue ? ++x, ++y : --x, --y;

    std::cout << x << " " << y << std::endl;
    return 0;
}

結果:

11 10

一方、someValue = falseそれを印刷している間:

9 9

なぜC ++コンパイラは、3項演算子のtrueブランチの場合xにのみインクリメントするコードを生成しますが、3項演算子のfalseブランチの場合は両方xをデクリメントしyますか?

私はこのように真の枝を括弧で囲んでさえいます:

someValue ? (++x, ++y) : --x, --y;

しかし、それでも結果は11 10です。


5
「優先順位」は、C ++の単なる出現現象です。言語文法を直接見て、式がどのように機能するかを確認する方が簡単な場合があります。
Kerrek SB 2017

26
原則についてはあまり気にません。:-)ここでこれを尋ねる必要があるという事実は、コードが他のプログラマによるコードレビューに合格しないことを示しています。そのため、これが実際にどのよう機能するかについての知識が役に立たなくなります。もちろん、難読化されたCコンテストに参加したくない場合を除きます。
Bo Persson

5
@BoPerssonは、このような例から学ぶ必要がないため、今後のレビュー担当者はこれを本番環境から拒否する必要がある理由を知ることはありません。
Leushenko

8
@Leushenko-警告のベルがとにかく鳴っているはずです。同じステートメント内の複数の増分および減分(ding、ding、ding!)。if-else(ding、ding、ding!)を使用できる場合の三項演算子。待って、それらのカンマは恐ろしいカンマ演算子ですか?(ding、DING、DING!)これらすべての演算子で、いくつかの優先事項がありますか?(ding、ding、ding!)したがって、これを使用することはできません。それなら、なぜそれが何をしているのかを考えるのに時間を無駄にするのはなぜですか
Bo Persson 2017年

4
マイナーニット:の名前?条件演算子です。三項演算子という用語は、3つのオペランドを持つ演算子を意味します。条件演算子は三項演算子の一例ですが、言語には(理論的には)複数の三項演算子を含めることができます。
bta

回答:


122

以下のよう@Raketeは、その優れた答えで述べて、これはトリッキーです。少し付け加えたいと思います。

三項演算子の形式は次のとおりです。

論理OR式の ? : 代入式

したがって、次のマッピングがあります。

  • someValue論理OR式
  • ++x, ++y
  • ??? ある代入式は --x, --yかだけ--x

実際には--x割り当て式をコンマで区切られた2つの式として解析できないため(C ++の文法規則に従って)、割り当て式--x, --yとして処理できないためです。

これにより、3項(条件付き)式の部分は次のようになります。

someValue?++x,++y:--x

読みやすくするために括弧で囲まれたものとして++x,++y計算することを検討するのに役立ちます。との間に含まれるものはすべて、条件付きのにシーケンス化さます。(私は残りのポストのためにそれらを括弧で囲みます)。(++x,++y)?:

そしてこの順序で評価されます:

  1. someValue?
  2. (++x,++y)または--xbool1の結果によって異なります。)

この式は、コンマ演算子の左側の部分式として扱われ、右側の部分式--yは次のようになります。

(someValue?(++x,++y):--x), --y;

つまり、左側は破棄された値の式であり、これは確実に評価されますが、右側を評価してそれを返します。

ときに何が起こるのsomeValueですかtrue

  1. (someValue?(++x,++y):--x)実行され、刻みxyなるように11し、11
  2. 左の式は破棄されます(増分の副作用は残ります)
  3. :私たちは、コンマ演算子の右側に評価--yし、デクリメントyへ戻ります10

行動「修正」するには、グループができ--x, --y、括弧付きはにそれを変換するための一次式であるため、有効なエントリ代入表現 *:

someValue?++x,++y:(--x, --y);

*これは、代入式を1次式に接続するかなりおかしい長いチェーンです。

割り当て式 ---(次で構成できます)-> 条件式 -> 論理式または式 -> 論理式 -> 包含的式 -> 排他的式 - -> and- -> 等価式 -> 関係式 -> シフト式 -> 加法式 -> 乗法式 -> pm式 -> キャスト式 -> 単項式 -> 後置式 -> 一次式


10
文法規則を解く手間をかけてくれてありがとう。そうすることで、C ++の文法には、ほとんどの教科書で見つかる以上のものが存在することがわかります。
sdenham 2017年

4
@sdenham:「式指向言語」が優れている理由(つまり{ ... }、式として処理できる場合)が尋ねられると、答えが出てきます=>このようなトリッキーな動作をするカンマ演算子を導入する必要がないようにする必要があります。
Matthieu M.17年

assignment-expressionチェーンについて読むリンクをください。
MiP、2017

@MiP:私はそれを標準自体から持ち上げました、gram.exprの
AndyG

88

うわー、それはトリッキーです。

コンパイラーは式を次のように認識します。

(someValue ? (++x, ++y) : --x), --y;

三項演算子はを必要とし:、そのコンテキストではそれ自体で立つことはできませんが、その後、コンマが誤ったケースに属すべきである理由はありません。

これで、なぜその出力が得られるのかがわかりやすくなります。場合はsomeValuetrueで、それから++x++yそして--y効果的に変化しない、実行されますyが、に1を加えますx

場合はsomeValuefalseで、その後、--xおよび--y1によってそれらの両方をデクリメント、実行されます。


42

なぜC ++コンパイラーは、3項演算子の真の分岐に対してのみ増分するコードを生成するのですか? x

あなたは何が起こったのかを誤解しました。真の分岐は、xおよびの両方を増分しますy。ただし、yその直後は無条件にデクリメントされます。

これがどのように行われるかを次に示します。条件付き演算子はC ++のカンマ演算子よりも優先順位が高いため、コンパイラは式を次のように解析します。

   (someValue ? ++x, ++y : --x), (--y);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^

--yコンマの後の「孤立した」に注意してください。これは、y最初は増加していた減少につながるものです。

私はこのように真の枝を括弧で囲んでさえいます:

someValue ? (++x, ++y) : --x, --y;

あなたは正しい道にいましたが、間違ったブランチを括弧で囲みました:次のように、else-branchを括弧で囲むことでこれを修正できます:

someValue ? ++x, ++y : (--x, --y);

デモ(印刷物11 11)


5

問題は、3項式がコンマよりも実際に優先されないことです。実際、C ++は優先順位だけで正確に記述することはできません。それは、3項演算子とコンマの間の相互作用であり、分解されます。

a ? b++, c++ : d++

として扱われます:

a ? (b++, c++) : d++

(カンマは優先順位が高いかのように動作します)。一方、

a ? b++ : c++, d++

として扱われます:

(a ? b++ : c++), d++

そして、三項演算子が優先されます。


真ん中の行に対して有効な解析は1つしかないので、これは依然として優先順位の範囲内にあると思います。それでもなお役立つ例
sudo rm -rf slash

2

回答で見落とされている点(コメントには触れましたが)は、実際のコードでは条件演算子が常に2つの値の1つを変数に割り当てるためのショートカットとして(設計で意図されていますか?)使用されていることです。

したがって、より大きなコンテキストは次のようになります。

whatIreallyWanted = someValue ? ++x, ++y : --x, --y;

これは一見馬鹿げているので、犯罪は多種多様です。

  • 言語は、割り当てでとんでもない副作用を許可します。
  • コンパイラは、あなたが奇妙なことをしていることを警告しませんでした。
  • 本は「トリック」の質問に焦点を当てているようです。後ろの答えが「この表現が何をするかは、誰も期待しない副作用を生み出すための不自然な例の奇妙なエッジケースに依存します。決してこれを行わない」であると期待することができます。

1
二つの変数のいずれかを割り当てると、三項演算子の通常の場合であるが、そこであり、の表現形式有することが有用である場合の機会はif(ループのために、例えば、インクリメント式)。より大きなコンテキストは、独立してfor (x = 0, y=0; x+y < 100; someValue?(++x, ++y) :( --x, --y))変更できるループを使用している可能性があります。xy
Martin Bonnerがモニカ

@MartinBonner確信はありませんが、この例はbo-perrsonの主張をかなりよくしているようです、と彼はTony Hoareを引用しています。
Taryn
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.