なぜswitchステートメントを文字列に適用できないのですか?


227

次のコードをコンパイルすると、のエラーが発生しましたtype illegal

int main()
{
    // Compilation error - switch expression of type illegal
    switch(std::string("raj"))
    {
    case"sda":
    }
}

switchまたはで文字列を使用することはできませんcase。どうして?文字列をオンにするのと同様のロジックをサポートするためにうまく機能する解決策はありますか?


6
MACROの背後にある列挙型のマップ構造を非表示にするブースト代替はありますか?
balki

@balkiブーストについてはよくわかりませんが、そのようなマクロを書くのは簡単です。Qtの場合、マッピングを非表示にできますQMetaEnum
phuclv

回答:


189

型システムと関係がある理由。C / C ++は文字列を型としてサポートしていません。定数文字配列の概念はサポートしていますが、文字列の概念を完全には理解していません。

switchステートメントのコードを生成するために、コンパイラーは、2つの値が等しいことの意味を理解する必要があります。intやenumのような項目の場合、これは簡単なビット比較です。しかし、コンパイラは2つの文字列値をどのように比較すべきでしょうか?大文字と小文字を区別する、区別しない、カルチャを認識するなど...文字列を完全に認識しないと、これに正確に答えることはできません。

また、C / C ++のswitchステートメントは通常、ブランチテーブルとして生成されます。文字列スタイルのスイッチのブランチテーブルを生成するのは、それほど簡単ではありません。


11
ブランチテーブルの引数は適用しないでください。これは、コンパイラの作成者が利用できる唯一の可能なアプローチです。本番コンパイラでは、スイッチの複雑さに応じて、いくつかのアプローチを頻繁に使用する必要があります。
台座

5
@plinth、私は歴史的な理由でそれを主にそこに置いた。「C / C ++がこれを行う理由」の質問の多くは、コンパイラの履歴から簡単に答えることができます。彼らが書いた当時、Cは美化されたアセンブリであり、スイッチは本当に便利なブランチテーブルでした。
JaredPar 2009年

114
コンパイラがifステートメントの2つの文字列値を比較する方法をどのようにして知ることができるかわからないが、switchステートメントで同じことをする方法を忘れているので、私は反対票を投じます。

15
最初の2つの段落は正当な理由ではないと思います。特にstd::stringリテラルが追加されたときのC ++ 14以降。それはほとんど歴史的です。しかし、気にしていない一つの問題は、方法がでていることでswitch、現在動作しますが、重複casesがなければならコンパイル時に検出されます。ただし、これは文字列ではそれほど簡単ではない場合があります(実行時のロケール選択などを考慮すると)。そのようなことは、constexprケースを必要とするか、不特定の動作を追加する必要があると思います(決して私たちがしたいことではありません)。
MM

8
2つのstd::string値を比較する方法、またはstd::stringconst char配列と比較する方法(つまり、operator ==を使用する方法)の明確な定義があります。コンパイラーがその演算子を提供する任意の型のswitchステートメントを生成できないようにする技術的な理由はありません。それはラベルの寿命のようなものについていくつかの質問を開きますが、これらすべてのすべては主に言語設計の決定であり、技術的な困難ではありません。
MikeMB 2017年

60

前に述べたように、コンパイラーはswitchステートメントを可能な限りO(1)タイミングに最適化するルックアップテーブルを作成することを好みます。これと、C ++言語には文字列型がないという事実を組み合わせてください。これstd::stringは、言語自体の一部ではない標準ライブラリの一部です。

私はあなたが考慮したいと思うかもしれない代替案を提供します、私は過去にそれを良い効果のために使ったことがあります。文字列自体を切り替えるのではなく、文字列を入力として使用するハッシュ関数の結果を切り替えます。事前に定義された文字列のセットを使用している場合、コードは文字列を切り替えるのとほぼ同じくらい明確です。

enum string_code {
    eFred,
    eBarney,
    eWilma,
    eBetty,
    ...
};

string_code hashit (std::string const& inString) {
    if (inString == "Fred") return eFred;
    if (inString == "Barney") return eBarney;
    ...
}

void foo() {
    switch (hashit(stringValue)) {
    case eFred:
        ...
    case eBarney:
        ...
    }
}

Cコンパイラがswitchステートメントを使用して実行する処理にほとんど従う明らかな最適化がたくさんあります。


15
実際にはハッシュ化していないので、これは本当に残念です。最新のC ++では、constexprハッシュ関数を使用して、コンパイル時に実際にハッシュできます。あなたの解決策は見た目はきれいですが、はしごが残念ながら続いている場合はすべて厄介です。以下のマップソリューションの方が優れており、関数呼び出しも回避できます。さらに、2つのマップを使用することで、エラーロギング用のテキストを組み込むこともできます。
Dirk Bester、2016年

:あなたはまた、ラムダと列挙型を避けることができstackoverflow.com/a/42462552/895245
チロSantilli郝海东冠状病六四事件法轮功

hashitはconstexpr関数ですか?std :: stringではなくconst char *を渡したとします。
ビクターストーン

しかし、なぜ?スイッチの上でifステートメントの実行を常に使用できます。どちらも影響は最小限ですが、スイッチを使用した場合のパフォーマンス上の利点はif-elseルックアップによって失われます。if-elseを使用すると、わずかに速くなりますが、さらに重要なことに、大幅に短くなります。
ゾーイ

20

C ++

constexprハッシュ関数:

constexpr unsigned int hash(const char *s, int off = 0) {                        
    return !s[off] ? 5381 : (hash(s, off+1)*33) ^ s[off];                           
}                                                                                

switch( hash(str) ){
case hash("one") : // do something
case hash("two") : // do something
}

1
すべてのケースが同じ値にハッシュしないことを確認する必要があります。そして、それでも、たとえばhash( "one")と同じ値にハッシュする他の文字列がスイッチの最初の "何か"を誤って実行するという、いくつかの間違いがあるかもしれません。
David Ljung Madison Stellar

私は知っていますが、同じ値にハッシュする場合、コンパイルされず、時間通りにそれに気づくでしょう。
Nick

良い点-しかし、それはあなたのスイッチの一部ではない他の文字列のハッシュ衝突を解決しません。場合によっては問題にならないこともありますが、これが一般的な「頼りになる」ソリューションである場合は、ある時点でセキュリティの問題などになると想像できます。
David Ljung Madison Stellar

7
operator ""コードをより美しくするためにを追加できます。デモのconstexpr inline unsigned int operator "" _(char const * p, size_t) { return hash(p); }ように使用してくださいcase "Peter"_: break;
hare1039

15

上記の@MarmouCorpではなく、http://www.codeguru.com/cpp/cpp/cpp_mfc/article.php/c4067/Switch-on-Strings-in-C.htmの C ++ 11アップデート

2つのマップを使用して、文字列とクラス列挙型の間で変換します(その値はスコープ内でスコープされるため、プレーン列挙型よりも優れています。また、素敵なエラーメッセージの逆引きも行われます)。

codeguruコードでのstaticの使用は、VS 2013 plusを意味するイニシャライザリストのコンパイラサポートで可能です。gcc 4.8.1はそれで問題ありませんでしたが、どれだけ前に互換性があるかはわかりません。

/// <summary>
/// Enum for String values we want to switch on
/// </summary>
enum class TestType
{
    SetType,
    GetType
};

/// <summary>
/// Map from strings to enum values
/// </summary>
std::map<std::string, TestType> MnCTest::s_mapStringToTestType =
{
    { "setType", TestType::SetType },
    { "getType", TestType::GetType }
};

/// <summary>
/// Map from enum values to strings
/// </summary>
std::map<TestType, std::string> MnCTest::s_mapTestTypeToString
{
    {TestType::SetType, "setType"}, 
    {TestType::GetType, "getType"}, 
};

...

std::string someString = "setType";
TestType testType = s_mapStringToTestType[someString];
switch (testType)
{
    case TestType::SetType:
        break;

    case TestType::GetType:
        break;

    default:
        LogError("Unknown TestType ", s_mapTestTypeToString[testType]);
}

文字列リテラルとコンパイル時の計算(C ++ 14または17だと思います)を必要とするソリューションを後で見つけたので、コンパイル時にケース文字列をハッシュし、実行時にスイッチ文字列をハッシュできることに注意してください。本当に長いスイッチの場合はおそらく価値がありますが、重要な場合は下位互換性はさらに低くなります。
Dirk Bester、

ここでコンパイル時のソリューションを教えていただけますか?ありがとう!
2019年

12

問題は、最適化のために、C ++のswitchステートメントがプリミティブ型以外には機能せず、コンパイル時の定数としか比較できないことです。

おそらく制限の理由は、コンパイラーがコードを1つのcmp命令とgotoにコンパイルする何らかの形の最適化を適用して、実行時にアドレスが引数の値に基づいて計算されるためです。分岐とループは最近のCPUではうまく機能しないため、これは重要な最適化になる可能性があります。

これを回避するには、ifステートメントに頼る必要があります。


文字列を処理できるスイッチステートメントの最適化されたバージョンは間違いなく可能です。彼らがプリミティブ型に使用するのと同じコードパスを再利用できないという事実はstd::string、他の人が言語で最初に市民を作って、効率的なアルゴリズムでスイッチステートメントでそれらをサポートできないことを意味しません。
ceztko 2018年

10

std::map +列挙型のないC ++ 11ラムダパターン

unordered_map償却される可能性がある場合O(1)C ++でHashMapを使用する最良の方法は何ですか?

#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

int main() {
    int result;
    const std::unordered_map<std::string,std::function<void()>> m{
        {"one",   [&](){ result = 1; }},
        {"two",   [&](){ result = 2; }},
        {"three", [&](){ result = 3; }},
    };
    const auto end = m.end();
    std::vector<std::string> strings{"one", "two", "three", "foobar"};
    for (const auto& s : strings) {
        auto it = m.find(s);
        if (it != end) {
            it->second();
        } else {
            result = -1;
        }
        std::cout << s << " " << result << std::endl;
    }
}

出力:

one 1
two 2
three 3
foobar -1

メソッド内での使用 static

このパターンをクラス内で効率的に使用するには、ラムダマップを静的に初期化するか、O(n)毎回支払うことでゼロから作成します。

ここでは{}staticメソッド変数の初期化を回避できます。クラスメソッドの静的変数ですが、以下で説明されているメソッドを使用することもできます。C++の静的コンストラクタ?プライベート静的オブジェクトを初期化する必要があります

ラムダコンテキストキャプチャ[&]を引数に変換する必要がありました、またはそれは定義されていませんでした:参照によるキャプチャで使用されるconst static auto lambda

上記と同じ出力を生成する例:

#include <functional>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

class RangeSwitch {
public:
    void method(std::string key, int &result) {
        static const std::unordered_map<std::string,std::function<void(int&)>> m{
            {"one",   [](int& result){ result = 1; }},
            {"two",   [](int& result){ result = 2; }},
            {"three", [](int& result){ result = 3; }},
        };
        static const auto end = m.end();
        auto it = m.find(key);
        if (it != end) {
            it->second(result);
        } else {
            result = -1;
        }
    }
};

int main() {
    RangeSwitch rangeSwitch;
    int result;
    std::vector<std::string> strings{"one", "two", "three", "foobar"};
    for (const auto& s : strings) {
        rangeSwitch.method(s, result);
        std::cout << s << " " << result << std::endl;
    }
}

3
このアプローチとswitchステートメントの間には違いがあることに注意してください。switchステートメント内のケース値の重複は、コンパイル時の失敗です。std::unordered_mapサイレントモードで使用すると、重複した値が受け入れられます。
D.Shawley 2018年

6

C ++およびCスイッチでは、整数型でのみ機能します。代わりにif elseラダーを使用してください。C ++は明らかに文字列に対してある種のswichステートメントを実装することができたでしょう-誰もそれを価値があるとは思っていなかったと思います、そして私はそれらに同意します。


合意された、しかし、あなたが使用するには、このことはできませんを作ったのか分からん
yesraaj

歴史?実数、ポインター、および構造体(Cの他のデータ型のみ)をオンにしても、意味がありませんので、Cは整数に制限しました。

特に、暗黙的な変換を許可するクラスをオンにすると、本当に楽しい時間を過ごせます。
シャープトゥース2009年

6

何故なの?同等の構文と同じセマンティクスでスイッチ実装を使用できます。C言語は、すべてのオブジェクトと文字列オブジェクトを持っていませんが、文字列の中Cでヌルポインタが参照する文字列を終了しました。C++言語は、オブジェクトの比較ですまたはオブジェクトの等式をチェックするための機能を過負荷にする可能性があります。のために文字列のようにスイッチを持って柔軟に十分である ための支援comparaisonまたはチェック平等という言語と任意の型のオブジェクトのための言語。そして現代では、このスイッチの実装を十分に効果的にすることができます。CC++CC++C++11

コードは次のようになります。

std::string name = "Alice";

std::string gender = "boy";
std::string role;

SWITCH(name)
  CASE("Alice")   FALL
  CASE("Carol")   gender = "girl"; FALL
  CASE("Bob")     FALL
  CASE("Dave")    role   = "participant"; BREAK
  CASE("Mallory") FALL
  CASE("Trudy")   role   = "attacker";    BREAK
  CASE("Peggy")   gender = "girl"; FALL
  CASE("Victor")  role   = "verifier";    BREAK
  DEFAULT         role   = "other";
END

// the role will be: "participant"
// the gender will be: "girl"

たとえばstd::pairs、より複雑な型や、等値演算(またはクイックモードの比較)をサポートする構造体やクラスを使用できます。

特徴

  • 比較または同等性のチェックをサポートする任意のタイプのデータ
  • カスケードされた入れ子のスイッチ状態を構築する可能性。
  • ケースステートメントを破るか、失敗する可能性
  • 非定数のケース式を使用する可能性
  • ツリー検索を使用して静的/動的モードをすばやく有効にすることができます(C ++ 11の場合)

言語切り替えによる構文の違いは

  • 大文字のキーワード
  • CASEステートメントには括弧が必要です
  • セミコロン ';' ステートメントの最後は許可されていません
  • CASEステートメントでのコロン「:」は許可されていません
  • CASEステートメントの最後にBREAKまたはFALLキーワードのいずれかが必要

以下のためC++97の言語線形検索を使用していました。用C++11して使用することも可能で、より現代的なquickツリー検索wuthモードをリターン CASE内のステートメントが許可されていませんになります。Cどこ言語の実装が存在char*種類とゼロで終了する文字列のcomparisionsが使用されています。

このスイッチの実装の詳細ご覧ください。


6

可能な限り最も単純なコンテナーを使用してバリエーションを追加するには(順序付けされたマップは必要ありません)... enumに悩まされることはありません-コンテナー定義をスイッチの直前に置くだけで、どの番号が表すかを簡単に確認できますどの場合。

これはでハッシュされたルックアップを実行し、unordered_mapを使用しintてswitchステートメントを実行します。かなり速いはずです。そのコンテナを作成したので、のat代わりにが使用されていることに注意してください。使用は危険な場合があります。文字列がマップにない場合、新しいマッピングを作成し、結果が未定義になるか、マップが継続的に成長する可能性があります。[]const[]

at()文字列がマップにない場合、関数は例外をスローすることに注意してください。したがって、最初にを使用してテストすることをお勧めしますcount()

const static std::unordered_map<std::string,int> string_to_case{
   {"raj",1},
   {"ben",2}
};
switch(string_to_case.at("raj")) {
  case 1: // this is the "raj" case
       break;
  case 2: // this is the "ben" case
       break;


}

未定義の文字列をテストするバージョンは次のとおりです。

const static std::unordered_map<std::string,int> string_to_case{
   {"raj",1},
   {"ben",2}
};
// in C++20, you can replace .count with .contains
switch(string_to_case.count("raj") ? string_to_case.at("raj") : 0) {
  case 1: // this is the "raj" case
       break;
  case 2: // this is the "ben" case
       break;
  case 0: //this is for the undefined case

}

4

tomjenが言ったように、Cの文字列はプリミティブ型ではないことが理由だと思います。文字列はchar配列として考えるので、次のようなことはできません。

switch (char[]) { // ...
switch (int[]) { // ...

3
検索しないと、文字配列はchar *に退化して、直接整数型に変換されます。だから、コンパイルはうまくいくかもしれませんが、確かにあなたが望むことはしません。
David Thornley、

3

C ++では、文字列は一流の市民ではありません。文字列操作は標準ライブラリを介して行われます。それが理由だと思います。また、C ++はブランチテーブル最適化を使用して、switch caseステートメントを最適化します。リンクを見てください。

http://en.wikipedia.org/wiki/Switch_statement


2

C ++では、intおよびcharに対してのみswitchステートメントを使用できます


3
charもintになります。
ストレッジャー2009年

ポインタもできます。つまり、別の言語で意味のあるものをコンパイルできることもありますが、正しく動作しません。
David Thornley、

あなたが実際に使用することができるlonglong longされ、ないだろうに変わりますint。そこで切り捨てられるリスクはありません。
MSalters 2015


0
    cout << "\nEnter word to select your choice\n"; 
    cout << "ex to exit program (0)\n";     
    cout << "m     to set month(1)\n";
    cout << "y     to set year(2)\n";
    cout << "rm     to return the month(4)\n";
    cout << "ry     to return year(5)\n";
    cout << "pc     to print the calendar for a month(6)\n";
    cout << "fdc      to print the first day of the month(1)\n";
    cin >> c;
    cout << endl;
    a = c.compare("ex") ?c.compare("m") ?c.compare("y") ? c.compare("rm")?c.compare("ry") ? c.compare("pc") ? c.compare("fdc") ? 7 : 6 :  5  : 4 : 3 : 2 : 1 : 0;
    switch (a)
    {
        case 0:
            return 1;

        case 1:                   ///m
        {
            cout << "enter month\n";
            cin >> c;
            cout << endl;
            myCalendar.setMonth(c);
            break;
        }
        case 2:
            cout << "Enter year(yyyy)\n";
            cin >> y;
            cout << endl;
            myCalendar.setYear(y);
            break;
        case 3:
             myCalendar.getMonth();
            break;
        case 4:
            myCalendar.getYear();
        case 5:
            cout << "Enter month and year\n";
            cin >> c >> y;
            cout << endl;
            myCalendar.almanaq(c,y);
            break;
        case 6:
            break;

    }

4
このコードは質問に答えることがありますが、このコードが質問に答える理由方法に関する追加のコンテキストを提供すると、長期的な価値が向上します。
ベンジャミンW.

0

多くの場合、文字列から最初の文字を引き出してそれをオンにすることで、余分な作業を追加できます。ケースが同じ値で始まる場合、charat(1)で入れ子のスイッチを実行する必要があるかもしれません。あなたのコードを読んでいる人なら誰でもヒントをいただければ幸いです。


0

スイッチの問題に対するより機能的な回避策:

class APIHandlerImpl
{

// define map of "cases"
std::map<string, std::function<void(server*, websocketpp::connection_hdl, string)>> in_events;

public:
    APIHandlerImpl()
    {
        // bind handler method in constructor
        in_events["/hello"] = std::bind(&APIHandlerImpl::handleHello, this, _1, _2, _3);
        in_events["/bye"] = std::bind(&APIHandlerImpl::handleBye, this, _1, _2, _3);
    }

    void onEvent(string event = "/hello", string data = "{}")
    {
        // execute event based on incomming event
        in_events[event](s, hdl, data);
    }

    void APIHandlerImpl::handleHello(server* s, websocketpp::connection_hdl hdl, string data)
    {
        // ...
    }

    void APIHandlerImpl::handleBye(server* s, websocketpp::connection_hdl hdl, string data)
    {
        // ...
    }
}

-1

スイッチケースでは文字列を使用できません。intおよびcharのみが許可されます。代わりに、文字列を表す列挙型を試して、スイッチケースブロックで次のように使用できます。

enum MyString(raj,taj,aaj);

swich caseステートメントで使用してください。



-1

スイッチは、整数型(int、char、boolなど)でのみ機能します。マップを使用して文字列と数値をペアにし、その数値をスイッチで使用してみませんか?


-2

これは、C ++がスイッチをジャンプテーブルに変換するためです。入力データに対して簡単な操作を実行し、比較せずに適切なアドレスにジャンプします。文字列は数値ではなく数値の配列なので、C ++は文字列からジャンプテーブルを作成できません。

movf    INDEX,W     ; move the index value into the W (working) register from memory
addwf   PCL,F       ; add it to the program counter. each PIC instruction is one byte
                    ; so there is no need to perform any multiplication. 
                    ; Most architectures will transform the index in some way before 
                    ; adding it to the program counter

table                   ; the branch table begins here with this label
    goto    index_zero  ; each of these goto instructions is an unconditional branch
    goto    index_one   ; of code
    goto    index_two
    goto    index_three

index_zero
    ; code is added here to perform whatever action is required when INDEX = zero
    return

index_one
...

(wikipedia https://en.wikipedia.org/wiki/Branch_tableからのコード)


4
C ++は、その構文の特定の実装を必要としません。ナイーブcmp/ jcc実装は、C ++標準に従って同じように有効です。
ルスラン2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.