次のコードをコンパイルすると、type illegal
のエラーが発生しました。
int main()
{
// Compilation error - switch expression of type illegal
switch(std::string("raj"))
{
case"sda":
}
}
switch
とcase
のどちらにもstringは使用できません。どうして?文字列をオンにするのと同じようなロジックをサポートするためにうまく機能する解決策はありますか?
なぜ型システムと関係があるのか。 C/C++は、文字列を型として実際にはサポートしていません。これは定数のchar配列の概念をサポートしていますが、文字列の概念を完全には理解していません。
Switch文のコードを生成するために、コンパイラは2つの値が等しいということが何を意味するのかを理解する必要があります。 intやenumのようなアイテムの場合、これはちょっとした比較です。しかし、コンパイラは2つの文字列値をどのように比較すべきですか?大文字と小文字を区別する、鈍感な、文化を認識する、など。
また、C/C++のswitchステートメントは通常、 branch tables として生成されます。文字列スタイルの切り替え用のブランチテーブルを生成するのはそれほど簡単ではありません。
前述のように、コンパイラは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文を使って行うことにほぼ従っている明白な最適化がたくさんあります。
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日。
明らかに上記の@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]);
}
問題は、最適化のためにC++のswitchステートメントはプリミティブ型以外では機能せず、コンパイル時定数とのみ比較できることです。
おそらく、この制限の理由は、コンパイラがコードを1つのcmp命令と、実行時に引数の値に基づいてアドレスが計算されるgotoにコンパイルする何らかの形式の最適化を適用できることにあります。分岐とループは現代のCPUではうまく動作しないので、これは重要な最適化になります。
これを回避するには、if文に頼らなければならないでしょう。
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
}
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;
}
}
C++およびCでは、スイッチは整数型でのみ機能します。代わりにif elseラダーを使用してください。 C++は明らかに文字列に対してある種のswichステートメントを実装することができたでしょう - 私は誰もそれが価値があるとは思わなかったと思います、そして私は彼らに同意します。
何故なの?同等の構文と同じセマンティクスで 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++97
言語では線形検索を使用しました。 C++11
およびより現代的なquick
モードを使用する場合は、CASEのreturnステートメントが許可されなくなります。 char*
型とゼロで終わる文字列の比較が使用される場合、C
言語実装が存在します。
読む 詳細 このスイッチの実装。
可能な限り単純なコンテナを使用してバリエーションを追加するには(順序付きマップは必要ありません)...列挙型を気にする必要はありません - 切り替えの直前にコンテナ定義を配置するだけなので、どの番号が表すのかわかりやすくなります。どちらの場合.
これは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
}
Tomjenが言ったように、Cでは文字列はプリミティブ型ではないので、文字配列をchar配列として考えているので、次のようなことはできません。
switch (char[]) { // ...
switch (int[]) { // ...
C++では文字列は一流の市民ではありません。文字列操作は標準ライブラリを介して行われます。それが理由だと思います。また、C++は分岐テーブル最適化を使用してswitch caseステートメントを最適化します。リンクを見てください。
C++では、intとcharに対してのみswitch文を使用できます。
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;
}
多くの場合、文字列から最初の文字を取り出してそれをオンにすることで余分な作業を避けることができます。あなたのケースが同じ値で始まっているなら、charat(1)で入れ子になった切り替えをしなければならなくなるかもしれません。あなたのコードを読む人は誰でもヒントをいただければ幸いです。
スイッチ問題に対するより機能的な回避策:
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)
{
// ...
}
}
スイッチは整数型(int、char、boolなど)でのみ動作します。マップを使って文字列と数字を組み合わせてから、その数字をスイッチと組み合わせて使用しないのはなぜですか。
あなたはスイッチケースで文字列を使用することはできません。唯一のintとcharが許可されています。代わりに、あなたは文字列を表すためにenumを試してみて、スイッチケースブロックの中でそれを使うことができます。
enum MyString(raj,taj,aaj);
Swich case文に使用してください。