if(0)を使用して、動作するはずのスイッチのケースをスキップしていますか?


122

C ++のswitchステートメントの2つのケースで、両方とも3番目のケースに分類したいという状況があります。具体的には、2番目のケースは3番目のケースにフォールスルーし、最初のケースも2番目のケース通過せずに3番目のケースにフォールスルーします。

私はばかげた考えを持っていて、それを試しました、そしてそれはうまくいきました!2番目のケースをif (0) {...で包みました}。次のようになります。

#ifdef __cplusplus
#  include <cstdio>
#else
#  include <stdio.h>
#endif

int main(void) {
    for (int i = 0; i < 3; i++) {
        printf("%d: ", i);
        switch (i) {
        case 0:
            putchar('a');
            // @fallthrough@
            if (0) {        // fall past all of case 1 (!)
        case 1:
            putchar('b');
            // @fallthrough@
            }
        case 2:
            putchar('c');
            break;
        }
        putchar('\n');
    }
    return 0;
}

それを実行すると、目的の出力が得られます。

0: ac
1: bc
2: c

CとC ++の両方(両方ともclangを使用)で試してみましたが、同じことをしました。

私の質問は次のとおりです。これは有効なC / C ++ですか?それはそれがすることをすることになっていますか?


34
はい、これは有効であり、Duffのデバイスとほぼ同じ理由で機能します。
dxiv

42
このようなコードを使用すると、読みやすさと保守性を少しでも気にする環境で、コードのウォークスルーから抜け出すことができます。
AndrewHenle20年

16
これは恐ろしいです。ダフのデバイスよりもさらに恐ろしいです、気をつけてください。関連して、私も最近のようなものを見ましたswitch(x) { case A: case B: do_this(); if(x == B) also_do_that(); ... }。それも、IMO、ひどいものでした。2か所で1行繰り返す必要がある場合でも、そのようなものをステートメントのように書き出してください。関数と変数(およびドキュメント!)を使用して、後で誤って1か所で更新するリスクを減らします。
ilkkachu

50
:-)そのコードを見たために怪我をしたり、傷ついたりした人にとって、それが良い考えだとは言いませんでした。実際、私はそれはばかげた考えだと言いました。
マークアドラー

4
このようなスイッチ内の構造は、RAIIではうまく機能しないことに注意してください:(
MooingDuck20年

回答:


58

はい、これは許可されており、あなたが望むことをします。以下のためswitchの文、C ++標準は言います

ケースラベルとデフォルトラベル自体は制御の流れを変更せず、そのようなラベル全体で妨げられることはありません。スイッチを終了するには、breakを参照してください。

[注1:通常、切り替えの対象となるサブステートメントは複合であり、大文字と小文字およびデフォルトのラベルは、(複合)サブステートメントに含まれる最上位のステートメントに表示されますが、これは必須ではありません。宣言は、switchステートメントのサブステートメントに表示できます。—エンドノート]

したがって、ifステートメントが評価されると、制御フローはif、介在するケースラベルに関係なく、ステートメントのルールに従って進行します。


13
この特定のケースとして、Boost.Coroutinesで、このルール(およびC ++の他の6つのコーナーケース)を利用してC ++ 03ルールを使用してコルーチンを実装する胃をかき回すマクロのセットを確認できます。それらはC ++ 20まで言語の一部にはなりませんでしたが、これらのマクロはそれらを機能させました...あなたが最初に制酸剤を服用している限り!
コートアンモン

60

はい、これは機能するはずです。Cのswitchステートメントのcaseラベルは、gotoラベルとほぼ同じです(ネストされたswitchステートメントでの動作についていくつかの注意点があります)。特に、それら自体は「ケース内」であると考えるステートメントのブロックを定義しておらず、gotoの場合と同じように、それらを使用してブロックの中央にジャンプできます。ブロックの途中にジャンプする場合、変数の初期化のジャンプなどに関して、gotoと同じ警告が適用されます。

そうは言っても、実際には、次のようにgotoステートメントでこれを書く方がおそらくより明確です。

    switch (i) {
    case 0:
        putchar('a');
        goto case2;
    case 1:
        putchar('b');
        // @fallthrough@
    case2:
    case 2:
        putchar('c');
        break;
    }

1
「ブロックの真ん中にジャンプするとき、変数の初期化のジャンプなどに関して、gotoと同じ警告が適用されます」それらはどのような警告ですか?変数の初期化を飛び越えることはできません
翼のある小惑星

4
@AsteroidsWithWingsは、標準に準拠する観点から、またはコンパイラが許可しないような実用的な観点から「できない」という意味ですか?私のGCCはCモードでそれを許可しているので、警告があります。ただし、C ++モードでは許可されません。
ilkkachu

5
@quetzalcoatl gcc-9とclang-6はどちらもこのコードを許可し、初期化されていない可能性があることを警告しbeeます(Cモードでは、C ++では「switchステートメントからこの場合はラベル/ジャンプは変数の初期化をバイパスできません」でエラーになります)。
ルスラン

7
gotoよりクリーンなソリューションを使用しているときに何か間違ったことをしていることを知っています。
KonradRudolph20年

9
@KonradRudolph:goto非常に悪意がありますが、手動で複雑な制御構造を作成していなければ、特に問題はありません。「有害と見なされる後藤」全体は、コンパイラーによって発行されたasmのように見えるHLL(Cなど)コードの記述に関するものでした(あちこちでjmpを行ったり来たりします)。フォワードのみ(概念的にはbreak初期と変わらないreturn)、再試行ループのみ(共通のフローパスの不明瞭化を回避し、ネストされたcontinue
R.。 GitHub STOP HELPINGICE20年

28

他の回答が述べているように、これは標準で技術的に許可されていますが、コードの将来の読者にとっては非常に混乱し、不明確です。

これが、switch ... caseステートメントが通常、多くのインラインコードではなく、関数呼び出しを使用して記述される必要がある理由です。

switch(i) {
case 0:
    do_zero_case(); do_general_stuff(); break;
case 1:
    do_one_case(); do_general_stuff(); break;
case 2:
    do_general_stuff(); break;
default:
    do_default_not_zero_not_one_not_general_stuff(); break;
}

質問のコードはdo_general_stuffデフォルトではなく、ケース2(および0と1)のみであることに注意してください。iただし、0..2の範囲外でスイッチを実行することはありません。
PeterCordes20年

@ PeterCordes-回答のコードはdo_general_stuffデフォルトではなく、ケース2(および0と1)のみであることに注意してください。
ピートベッカー

提案-おそらくデフォルトのケースを削除するか、名前を変更する必要がありますか?さまざまな関数名を見逃しがちです。
I.Am.A.Guy20年

@PeteBecker:ああ、私はそれを逃したかIDKgeneraldefault異なる言葉でした。OTOH、真ん中の文字がスクランブルされていれば、人間は通常も単語を読むことができるということは既知の事実です。私たちは最初と最後を見る傾向があるので、真ん中だけが異なることはおそらくスキミング性にとって理想的ではありません。おそらくdo_プレフィックスを削除します。
PeterCordes20年

1
@supercat:その「よく知られている事実」は、真ん中の文字を別の文字に置き換えることではなく、そのときの順序を混同することです。「もしシアムがグラニールで真実であるなら、あなたはティをレイドすることもあるでしょう。」
MichaelKarcher20年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.