web-dev-qa-db-ja.com

switch文が文字列に適用できないのはなぜですか?

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

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

switchcaseのどちらにもstringは使用できません。どうして?文字列をオンにするのと同じようなロジックをサポートするためにうまく機能する解決策はありますか?

194
yesraaj

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

Switch文のコードを生成するために、コンパイラは2つの値が等しいということが何を意味するのかを理解する必要があります。 intやenumのようなアイテムの場合、これはちょっとした比較です。しかし、コンパイラは2つの文字列値をどのように比較すべきですか?大文字と小文字を区別する、鈍感な、文化を認識する、など。

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

168
JaredPar

前述のように、コンパイラは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文を使って行うことにほぼ従っている明白な最適化がたくさんあります。

53
D.Shawley

Int、char、enumなどのプリミティブの切り替えのみ使用できます。あなたが望むようにそれをする最も簡単な解決策は、enumを使うことです。

#include <map>
#include <string>
#include <iostream.h>

// Value-Defintions of the different String values
static enum StringValue { evNotDefined,
                          evStringValue1,
                          evStringValue2,
                          evStringValue3,
                          evEnd };

// Map to associate the strings with the enum values
static std::map<std::string, StringValue> s_mapStringValues;

// User input
static char szInput[_MAX_PATH];

// Intialization
static void Initialize();

int main(int argc, char* argv[])
{
  // Init the string map
  Initialize();

  // Loop until the user stops the program
  while(1)
  {
    // Get the user's input
    cout << "Please enter a string (end to terminate): ";
    cout.flush();
    cin.getline(szInput, _MAX_PATH);
    // Switch on the value
    switch(s_mapStringValues[szInput])
    {
      case evStringValue1:
        cout << "Detected the first valid string." << endl;
        break;
      case evStringValue2:
        cout << "Detected the second valid string." << endl;
        break;
      case evStringValue3:
        cout << "Detected the third valid string." << endl;
        break;
      case evEnd:
        cout << "Detected program end command. "
             << "Programm will be stopped." << endl;
        return(0);
      default:
        cout << "'" << szInput
             << "' is an invalid string. s_mapStringValues now contains "
             << s_mapStringValues.size()
             << " entries." << endl;
        break;
    }
  }

  return 0;
}

void Initialize()
{
  s_mapStringValues["First Value"] = evStringValue1;
  s_mapStringValues["Second Value"] = evStringValue2;
  s_mapStringValues["Third Value"] = evStringValue3;
  s_mapStringValues["end"] = evEnd;

  cout << "s_mapStringValues contains "
       << s_mapStringValues.size()
       << " entries." << endl;
}

コードを書く Stefan Ruck、2001年7月25日。

29
MarmouCorp

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

2つのマップを使用して、文字列とクラスenumの間の変換を行います(値の範囲がスコープ内にあるため、単純なenumよりも優れています。また、Niceエラーメッセージの逆引き参照)。

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]);
}
14
Dirk Bester

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

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

これを回避するには、if文に頼らなければならないでしょう。

11
tomjen

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
}
9
Nick

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

潜在的な償却のためのunordered_mapO(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;
    }
}

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

6
anon

何故なの?同等の構文と同じセマンティクスで switch implementation を使用できます。 C言語にはオブジェクトと文字列オブジェクトがまったくありませんが、Cの文字列は、ポインターによって参照されるヌル終了文字列です。 C++言語には、オブジェクトのオーバーロード関数を作成したり、オブジェクトの等価性をチェックしたりする可能性があります。 C as C++は、C言語の文字列や、C++言語の比較または同等性のチェックをサポートする任意のタイプのオブジェクトに対して、このようなスイッチを使用するのに十分な柔軟性があります。また、最新の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などのより複雑な型、または等値演算(またはquickモードの比較)をサポートする構造体またはクラスを使用することができます。

特徴

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

言語切り替えとのシンタックスの違いは

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

C++97言語では線形検索を使用しました。 C++11およびより現代的なquickモードを使用する場合は、CASEのreturnステートメントが許可されなくなります。 char*型とゼロで終わる文字列の比較が使用される場合、C言語実装が存在します。

読む 詳細 このスイッチの実装。

5
oklas

可能な限り単純なコンテナを使用してバリエーションを追加するには(順序付きマップは必要ありません)...列挙型を気にする必要はありません - 切り替えの直前にコンテナ定義を配置するだけなので、どの番号が表すのかわかりやすくなります。どちらの場合.

これは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}
};
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
rsjaffe

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

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

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

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

3
chappar

C++では、intとcharに対してのみswitch文を使用できます。

3
CodeMonkey1313
    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;

    }
0
Juan Llanes

多くの場合、文字列から最初の文字を取り出してそれをオンにすることで余分な作業を避けることができます。あなたのケースが同じ値で始まっているなら、charat(1)で入れ子になった切り替えをしなければならなくなるかもしれません。あなたのコードを読む人は誰でもヒントをいただければ幸いです。

0
Marshall Taylor

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

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)
    {
        // ...
    }
}
0
FelikZ

スイッチは整数型(int、char、boolなど)でのみ動作します。マップを使って文字列と数字を組み合わせてから、その数字をスイッチと組み合わせて使用​​しないのはなぜですか。

0
derpface

あなたはスイッチケースで文字列を使用することはできません。唯一のintとcharが許可されています。代わりに、あなたは文字列を表すためにenumを試してみて、スイッチケースブロックの中でそれを使うことができます。

enum MyString(raj,taj,aaj);

Swich case文に使用してください。

0
anil